2020.11.31 選拔賽題解

訓練賽地址ios

A (線段樹or暴力)

題目連接
⭐⭐⭐c++

題意:
有一個長爲\(L\)的停車道,座標從0開始,車輛停放時車頭朝向正方向,且距離車頭\(f\)範圍內和距離車尾\(b\)範圍內不能有其它車輛。
有兩種操做:數組

  1. 「1 \(a\)」,有1輛長度爲\(a\)的車開進來找停車位。若是能找到輸出最小的起始位置,不然輸出-1.
  2. 「2 \(a\)」,第\(i\)個操做中開進來的車開出去了(保證第\(i\)個操做是1操做)

解析:函數

  • 作法一(線段樹)
    考慮創建線段樹,維護區間內最大無車區域\(mx\),左端點向內最大無車區域\(ls\),右端點向內最大無車區域\(rs\),以及\(lazy\)標籤
  1. 對於\(update\)函數,用\(lazy\)維護維護區間修改值,,進行基礎的區間修改便可,當修改區間徹底被包含在線段區間內,同時更新\(mx,ls,rs\)
  2. 對於\(query\)函數,考慮左端點向內無車區域、左區間+右區間無車區域,右端點向內無車區域三种放置車輛狀況
    • 對於左端點向內無車區域能夠放置的狀況,對左區間進行遞歸查詢
    • 對於左區間+右區間能夠放置的狀況,返回【左區間右端點-對應的\(rs\)值】
    • 對於右端點向內無車區域能夠放置的狀況,對右區間進行遞歸查詢
  3. 對於\(pushdown\)函數,\(lazy\)標籤下移,更改維護區段值
  4. 對於\(pushup\)函數,父區間\(ls\)承接左區間\(ls\),父區間\(rs\)承接右區間\(rs\),且若是左區間均可用,則能夠延伸到右區間的\(ls\),同理,若是右區間均可用,則能夠延伸到左區間的\(rs\),而父區間對應的\(mx\),則要知足\(mx=\max(mx_l,mx_r,rs_l+ls_r)\)
  5. 因爲整個\(L\)兩端的汽車放置不受\(b,f\)的影響,所以能夠將\(L\)拓展\(L+b+f\),這樣處理還避免了\(query\)查詢中,若是初始整個區間都要被使用的狀況。
    注意:
    1. \(query\)函數中,必定要按所給順序進行查詢,保證是在最左端插入可放置車輛
    2. 對於返回值而言,因爲總體\((b+a+f)\)插入位置比線段樹位置少了\(b\)\(L\)改變所致使),而車尾位置又比總體插入位置多了b,相抵,即可以獲得線段樹位置即爲車輛車尾對應的位置
#include<bits/stdc++.h>
using namespace std;
/*===========================================*/

struct Tree
{
	int l, r, lazy, ls, rs, mx;
};

const int maxn = 1e6 + 205;

struct Segment_Tree
{
	Tree tree[maxn << 2];

	void pushdown(int root)
	{
		if (~tree[root].lazy)
		{
			tree[root << 1].mx = tree[root << 1].ls = tree[root << 1].rs = (tree[root << 1].r - tree[root << 1].l) * tree[root].lazy;
			tree[root << 1 | 1].mx = tree[root << 1 | 1].ls = tree[root << 1 | 1].rs = (tree[root << 1 | 1].r - tree[root << 1 | 1].l) * tree[root].lazy;
			tree[root << 1].lazy = tree[root << 1 | 1].lazy = tree[root].lazy;
			tree[root].lazy = -1;
		}
	}
	void pushup(int root)
	{
		tree[root].ls = tree[root << 1].ls, tree[root].rs = tree[root << 1 | 1].rs;
		tree[root].mx = max(tree[root << 1].mx, tree[root << 1 | 1].mx);
		tree[root].mx = max(tree[root].mx, tree[root << 1].rs + tree[root << 1 | 1].ls);
		if (tree[root << 1].mx == tree[root << 1].r - tree[root << 1].l)
			tree[root].ls += tree[root << 1 | 1].ls;
		if (tree[root << 1 | 1].mx == tree[root << 1 | 1].r - tree[root << 1 | 1].l)
			tree[root].rs += tree[root << 1].rs;
	}

	void build(int k, int l, int r)
	{
		tree[k].l = l, tree[k].r = r;
		tree[k].ls = tree[k].rs = tree[k].mx = r - l;
		tree[k].lazy = -1;
		if (r - l != 1)
			build(k << 1, l, l + (r - l) / 2), build(k << 1 | 1, l + (r - l) / 2, r);
	}

	void update(int k, int ql, int qr, int val)
	{
		int& l = tree[k].l, & r = tree[k].r, & mx = tree[k].mx, & ls = tree[k].ls, & rs = tree[k].rs;
		if (ql <= l && r <= qr)
		{
			ls = rs = mx = (r - l) * val;
			tree[k].lazy = val;
		}
		else if (ql<r && qr>l)
		{
			pushdown(k);
			update(k << 1, ql, qr, val);
			update(k << 1 | 1, ql, qr, val);
			pushup(k);
		}
	}

	int query(int k, int val)
	{
		int& l = tree[k].l, & r = tree[k].r, & mx = tree[k].mx, & ls = tree[k].ls, & rs = tree[k].rs;
		if (tree[k << 1].mx >= val)
		{
			pushdown(k);
			return query(k << 1, val);
		}
		else if (tree[k << 1].rs + tree[k << 1 | 1].ls >= val)
			return tree[k << 1].r - tree[k << 1].rs;
		else if (tree[k << 1 | 1].mx >= val)
		{
			pushdown(k);
			return query(k << 1 | 1, val);
		}
		return -1;
	}

}sol;

pair<int, int> qu[105];

int main()
{
	//freopen("abc.in", "r", stdin);
	int L, b, f;
	int n, opt, a;
	while (~scanf("%d%d%d", &L, &b, &f))
	{
		L += b + f;
		sol.build(1, 0, L);
		scanf("%d", &n);
		for (int i = 1; i <= n; ++i)
		{
			scanf("%d%d", &opt, &a);
			if (opt == 1)
			{
				int ans = sol.query(1, a + b + f);
				printf("%d\n", ans);
				if (ans == -1) continue;
				qu[i].first = ans + b;
				qu[i].second = qu[i].first + a;
				sol.update(1, qu[i].first, qu[i].second, 0);
			}
			else
				sol.update(1, qu[a].first, qu[a].second, 1);
		}
	}
}
  • 作法二(暴力排序枚舉)
    查看題目能夠發現,詢問次數居然只有最多100次?What?! 也就是說停車場中的車輛也最多隻有100輛,因而這道題能夠直接暴力
  1. 因爲端點車輛放置不受\(b,f\)的影響,因此提早在數組中放置\([-b,-b]\)\([L+f,L+f]\)的兩輛汽車(這樣不用特判)
  2. 遇到1操做,\(O(n)\)查詢能夠最早能夠插入的位置,而後進行插入,插入位置\(i\)須要知足

\[car[i].l-car[i-1].r>=a+b+f \]

同時須要保存對應的操做數,操做結束後進行排序
3. 遇到2操做,\(O(n)\)查詢\(a\)所對應的車輛,進行刪除便可ui

#include<bits/stdc++.h>
using namespace std;
/*===========================================*/
struct Car
{
	int id;
	int l, r;
	bool operator<(const Car& a)const
	{
		return l < a.l;
	}
};

vector<Car> car;

void solve()
{
	int L, b, f, n;
	while (~scanf("%d%d%d", &L, &b, &f))
	{
		car.clear();
		car.push_back(Car{ -1, -b, -b });
		car.push_back(Car{ -1,L + f,L + f });
		int opt, a;
		scanf("%d", &n);
		for (int j = 1; j <= n; ++j)
		{
			scanf("%d%d", &opt, &a);
			if (opt == 1)
			{
				bool ok = false;
				for (int i = 1; i < car.size(); ++i)
				{
					if (car[i].l - car[i - 1].r >= a + b + f)
					{
						car.push_back(Car{ j,car[i - 1].r + b,car[i - 1].r + b + a });
						printf("%d\n", car[car.size() - 1].l);
						ok = true;
						sort(car.begin(), car.end());
						break;
					}
				}
				if (!ok)
					printf("-1\n");
			}
			else
				for (int i = 1; i < car.size(); ++i)
					if (car[i].id == a)
					{
						car.erase(car.begin() + i);
						break;
					}
		}
	}
}

int main()
{
	solve();
}

B(dfs構造)

題目連接
⭐⭐編碼

題意:
給你三個整數,\(n,d,k\)。你的任務是從新構建出一棵樹或者判斷出不能構造出這樣的樹。\(n\)表明這棵樹一共有\(n\)個點,\(d\)表明的是這棵樹的直徑爲\(d\)\(k\)是每一個最大節點度數爲\(k\)。若是可以構造出這樣的樹,輸出‘YES’,和全部的邊。若是不能,那麼輸出‘NO’。spa

解析:.net

  1. 首先構造出這條最長的直徑,接下來就是向這條直徑上的點上添加其餘的點同時保證直徑不會超過\(d\)便可
  2. 很明顯能夠發現,添加點的層數\(i\)要保證\(i\le \min(i-1,d+1-i)\)
  3. 這樣的話即可以用dfs進行指數級別的遞歸構造

注意:code

  1. 第一次進行構造時,因爲是從直徑上的點開始,因此可用度爲\(k-2\),而日後的構造可用度爲\(k-1\)
  2. \(k=1\wedge n>2\text{(only two points exist is allowed)}\qquad and \qquad n\le d\text{(construct the diam)}\)兩種狀況進行特判
  3. 遞歸裏寫法必定要注意前後順序,保證在達到目標數之後,再也不向數組中添加新答案,也不要繼續遞歸性查詢,不然會爆MLE
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;

int now;
typedef pair<int, int> P;
vector<P> v;
int n, d, k;

void dfs(int fa, int up, int lim)
{
	if (now == n || !up || !lim) return;
	while (up--)
	{
		v.push_back(P(fa, ++now));
		if (now == n) return;
		else dfs(now, k - 1, lim - 1);
		if (now == n) return;
	}
}

int main()
{
	scanf("%d%d%d", &n, &d, &k);
	if (k == 1 && n > 2 || n <= d)
	{
		printf("NO");
		return 0;
	}
	bool ok = false;
	now = d + 1;
	for (int i = 1; i <= d; ++i)
		v.push_back(P(i, i + 1));
	for (int i = 2; i <= d; ++i)
		dfs(i, k - 2, min(i - 1, d + 1 - i));
	if (now == n)
	{
		printf("YES\n");
		for (auto& i : v)
			printf("%d %d\n", i.first, i.second);
	}
	else
		printf("NO");

}

C(二分+貪心)

題目連接
⭐⭐排序

題目:
給出\(n\)個數,從中選出長度爲\(k\)的子序列造成環,若是相鄰兩個元素之和不超過\(M\),求解\(M_{min}\)

解析:

  1. 對於兩個數,若是他們的和不超過\(M\),則兩者至少有一個小於等於\(\lfloor\frac{M}{2}\rfloor\)
  2. 那麼即可以記錄這些\(x\)的位置\(i\ ,\{i\mid p[i]\le\lfloor\frac{M}{2}\rfloor\}\)貪心地在\((p[i],p[i-1])\)之間插入可行點,這樣就能夠得到對於某個\(M\)所能獲得的環內元素數量\(\Delta k\)的最大值
  3. 能夠獲得每一個\(M\)所對應的\(\Delta k_{max}\),即可以進行二分

注意:

  1. 因爲單個數字上限達到了\(10^9\),因此\(M\)會大於\(2\times10^9>INT_{max}\),所以必定要開long long
#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn=2e5+5;
int w[maxn];
int p[maxn];
int n,k;

int ok(long long m)
{
    int res=0;
    int cnt=0;
    for(int i=0;i<n;++i)
        if(w[i]<=m/2)
            p[cnt++]=i;
    if(cnt<=1)
        return cnt;
    for(int i=1;i<cnt;++i)
        for(int j=p[i-1]+1;j<p[i];++j)
            if(1ll*w[j]+max(w[p[i-1]],w[p[i]])<=m)
            {
                ++res;
                break;
            }
    for(int j=p[cnt-1]+1;j<n;++j)
        if(1ll*w[j]+max(w[p[cnt-1]],w[p[0]])<=m)
            return res+cnt+1;
    for(int j=0;j<p[0];++j)
        if(1ll*w[j]+max(w[p[cnt-1]],w[p[0]])<=m)
            return res+cnt+1;
    return res+cnt;
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;++i)
        scanf("%d",&w[i]);
    long long l=0,r=2e9+10,mid;
    while(l<r)
    {
        mid=l+(r-l)/2;
        if(ok(mid)>=k) r=mid;
        else l=mid+1;
    }
    printf("%lld",r);
}

D(打表找規律)

題目連接

題意:
給出¥1,¥5,¥10,¥50,四種鈔票,問\(n\)張鈔票能夠組成多少種不一樣的面額

解析:
初看題目很像一道dp,但發現\(n\)高達\(10^9\),果斷放棄
打表找規律不失爲一種好方法

注意:不開long long成神仙

總結:
經過打表發如今\(n>11\)時,答案是一個等差數列,公差爲49,這樣的話對\(n\le11\)部分進行特判,就解決了問題

打表代碼:

#include<bits/stdc++.h>
using namespace std;
/*===========================================*/

set<int> s;

void dfs(int x, int cur, int num)
{
	switch (x)
	{
	case 0:
		for (int i = 0; i <= cur; ++i)
			dfs(x + 1, cur - i, num + i);
		break;
	case 1:
		for (int i = 0; i <= cur; ++i)
			dfs(x + 1, cur - i, num + 5 * i);
		break;
	case 2:
		for (int i = 0; i <= cur; ++i)
			dfs(x + 1, cur - i, num + 10 * i);
		break;
	case 3:
		s.insert(num + cur * 50);
	}
}
#include<bits/stdc++.h>
using namespace std;
/*===========================================*/

LL ans[] = { 0,4,10,20,35,56,83,116,155,198,244,292 };

int main()
{
	LL n;
	scanf("%lld", &n);
	if (n <= 11)
		printf("%lld", ans[n]);
	else
		printf("%lld", ans[11] + 49 * (n - 11));
}

E(暴力+KMP)

題目連接
⭐⭐⭐

題意:
給出\(n\)個獨立的單詞。在任意兩個相鄰的單詞之間有且僅有一個空格。須要注意的是,在第一個單詞以前沒有空格,在最後一個單詞以後也沒有空格。
如今能夠進行一次操做,將重複出現過的單詞以及他們之間的空格進行組合,造成一個單詞組,每一個單詞用一個字母表示,例如"\(ab\ cd\ ab\ cd\ ee\rightarrow A A\ ee\)",這個單詞組在原所給單詞序列中出現須要多於一次
問進行這樣的操做後,求單詞序列長度的最小值

解析:

  1. \(map\)存儲每一個單詞所對應的編碼,這樣就將單詞組,壓縮成了一個只包含數字的數組。同時記錄每一個編碼所對應單詞的長度\(sz\)
  2. 暴力枚舉區間\([l,r]\),對區間內的編碼進行KMP預處理,查詢這段編碼在總體單詞序列中出現的次數\(cnt\)
  3. 那麼若是出現次數大於1的狀況下,寫出對應的單詞組替代狀況

\[\underbrace{\overbrace{word_1}^{sz_1}\ word_2\ \cdots\ word_n}_{\sum_{i=1}^nsz_i(words)+n-1(space)} \]

能夠發現減小了\((\sum_{i=1}^nsz_i)+n-1\),而增長了單詞數量所對應的表明字符\(n\),因此總的來講減小了\((\sum_{i=1}^nsz_i)-1\),對結果統計最小值便可

#include<bits/stdc++.h>
#define IOS std::ios::sync_with_stdio(0);cin.tie(0);
using namespace std;
/*===========================================*/

map<string, int> m;
const int maxn = 305;
int sz[maxn];
int a[maxn];
int nxt[maxn];
int cnt;


int main()
{
	IOS
	int n, res = 0;
	string s;
	cin >> n;
	for (int i = 0; i < n; ++i)
	{
		cin >> s;
		if (!m[s]) m[s] = ++cnt, sz[cnt] = s.size();
		a[i] = m[s];
		res += s.size();
	}
	res += n - 1;
	int total = res;
	for (int lf = 0; lf < n; ++lf)
	{
		int len = 0;
		for (int ri = lf; ri < n; ++ri)
		{
			len += sz[a[ri]];
			nxt[0] = -1;
			int cnt = 0;
			int i = 0, k = -1;
			while (i < ri - lf)
			{
				if (k == -1 || a[lf + i] == a[lf + k])
				{
					if (a[lf + ++i] == a[lf + ++k]) // 當兩個字符相等時要跳過
						nxt[i] = nxt[k];
					else
						nxt[i] = k;
				}
				else
					k = nxt[k];
			}
			nxt[ri - lf + 1] = 0;
			i = 0; // 主串的位置
			int j = 0; // 模式串的位置
			while (i < n)
			{
				if (j == -1 || a[i] == a[lf + j])
				{
					++i, ++j;
					if (j == ri - lf + 1)
					{
						++cnt;
						j = nxt[j];
						//操做
					}
				}
				else
					j = nxt[j];
			}
			if (cnt != 1)
				res = min(res, total - cnt * (len - 1));
		}
	}
	cout << res;
}
相關文章
相關標籤/搜索