1. 程式人生 > 實用技巧 >Codeforces Round #696 (Div. 2) A~D題解

Codeforces Round #696 (Div. 2) A~D題解

原題連結:Codeforces Round #696 (Div. 2)

A. Puzzle From the Future

顯然直接列舉每一位,能填1就填1.

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1e5+7;
char s[N],ans[N];

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		int n;scanf("%d",&n);
		scanf("%s",s + 1);
		int last = -1;
		forn(i,1,n)
		{
			int c = s[i] - '0';
			if(c + 1 == last)	ans[i] = '0',last = c;
			else	ans[i] = '1',last = c + 1;
		}
		forn(i,1,n)	printf("%c",ans[i]);
		puts("");
    }
    return 0;
}

B. Different Divisors

顯然取最近質數

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	

const int N = 1e6+7;
int primes[N],cnt;
bool st[N];
void init(int N)
{
    for(int i = 2;i <= N;++i)
    {
        if(!st[i])  primes[cnt++] = i;
        for(int j = 0;j < cnt && primes[j] * i <= N;++j)
        {
            st[primes[j] * i] = 1;
            if(i % primes[j] == 0)
            {
                break;
            }
        }
    }
}

int main()
{
	init(N - 1);
    int T;scanf("%d",&T);
    while(T--)
    {
		ll d;scanf("%lld",&d);
		int cur = 1;
		forn(i,0,cnt - 1)
		{
			if(cur == 1)
			{
				if(primes[i] - cur >= d)
					cur = primes[i];
			}
			else if(primes[i] - cur >= d)
			{
				printf("%lld\n",1ll*primes[i] * cur);
				break;
			}
		}
    }
    return 0;
}

C. Array Destruction

題目大意:給定一個長度為\(2*n\)的陣列,再最開始,選定一個數\(x\),接下來執行若干步操作:選定陣列中兩個不同位置的元素,且和為\(x\).將兩者從陣列中刪除,並更新\(x\)為兩者中的較大者.問是否能刪除整個陣列,如果可以,輸出一組可行的方案,如果不能輸出無解.

思路

不妨把每個數對寫作\((x,y)\)並且保證\(x\geq y\).顯然可以注意到的是每一步操作之後,勢必會讓一開始選定的\(x\)的值變小,記作\(x_1\),那麼顯然\(x_1\)必須要是整個陣列的最大值,因為每次操作之後都會變小,變小之後不可能再消除最大值.

現在的問題是,已知\(x1\)

,\(y1\)未知,其次\((x2,y2)\)等等之後的未知,需要求一組可行的方案滿足每一組\(x_i+y_i=x_{i-1}\).逐次考慮構造,因為每次都會讓最小值變小,所以一個行之有效的方案就是依次構造每個\(x\)的值的同時,保證選擇的是整個陣列中當前還沒有選過的最大的數,同時直接構造出對應的\(y\),因為有等式限制,如果沒有對應的\(y\)就輸出無解.

考慮如何得到\(y1\),一個錯誤的想法是先不管\(y1\)直接從\((x2,y2)\)開始構造,但是這樣會導致多解的存在,所以正確的方式是直接列舉\(y1\)的取值,之後整個局面就是固定的.整個列舉和\(check\)的過程佔到\(O(n^2)\)所以在每次尋找最大值和找最大值的時候使用其他資料結構查詢,例如\(map\),使查詢的複雜度降到\(O(logn)\)即可通過.

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	
typedef pair<int,int> pii;
#define x first
#define y second

const int N = 2005;
int a[N];
map<int,int> cnt;

inline void burn(int x)
{
	if(--cnt[x] == 0)	cnt.erase(x);
}

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
    	cnt = map<int,int>();
		int n;scanf("%d",&n);
		forn(i,1,2 * n)	scanf("%d",&a[i]);
		sort(a + 1,a + 2 * n + 1);
		
		int __r = 0;
    	for(int i = 1;i <= 2 * n - 1;++i)
    	{
    		cnt = map<int,int>();
    		forn(j,1,2 * n)	cnt[a[j]]++;
    		int target = a[2 * n],y1 = a[i];
    		burn(a[2 * n]);burn(a[i]);
    		int del = 2,ok = 1;
    		
    		vector<pii> op;
    		op.push_back({target,y1});
    		
    		while(del < 2 * n)
    		{
    			auto _from = cnt.rbegin();
    			int x = (*_from).first;burn(x);
    			if(!cnt.count(target - x))	{ok = 0;break;}
    			op.push_back({x,target - x});
    			burn(target - x);
    			target = x;
    			del += 2;
    		}
    		if(!ok)	continue;
    		printf("YES\n%d\n",op[0].x + op[0].y);
    		for(int i = 0;i < op.size();++i)	printf("%d %d\n",op[i].x,op[i].y);
    		__r = 1;
    		break;
    	}
    	if(!__r)	puts("NO");
    }
    return 0;
}

D. Cleaning

題目大意:有\(n\)堆石子,每堆石子各有\(a_i\)個,每次可以選取兩個相鄰的且都不空的石子堆裡同時拿走一個石子.如果有石子堆是空的,則相鄰關係不繼承.由於這個遊戲太不合理了,所以給你一次機會,在遊戲開始之前可以交換兩個相鄰的石子堆.問是否能移走所有的石子.

資料範圍:

\(2 \leq n \leq 2*10^5\)

\(1 \leq a_i \leq 10^9\)

思路

由於交換的條件比較吊比,先考慮一個子問題:沒有交換的時候如何判斷是否存在解.

值得注意的性質是:對於\(a_1\)來說如果想要消除這堆石子,那麼只能是讓\(a_1\)\(a_2\)相鄰的去刪除,刪除之後會讓\(a_1=0,a_2-=a_1\).同樣的對最右端也是對稱的,不難想到可以先預處理出在不使用交換的前提下,從左邊往右邊一個一個推,和右邊往左邊分別推到\(i\)這個位置會導致\(i\)這個位置上有多少個石子,分別記做\(L_i,R_i\).那麼有了這個預處理之後,在沒有交換的前提下,我們可以把左邊的一段操作到\(i\)這個點和右邊一段操作到\(i\)個點這兩部分合起來,也就是如果從第一個元素一直往右推,從右往左一直推,假如說有解的話,那麼應該有一個分界點\(i\)使得他倆合併起來之後有什麼相等條件表示左右兩邊都可以刪除完,比較顯然就是\(R_i-L_{i-1}=0\).

考慮加入交換條件,因為交換隻限於在兩個相鄰的元素之間交換,所以可以列舉任意一對數\((i,i+1)\),那麼根據上面的分析,我們可以把左邊的部分直接合並地看做是一個元素\(L[i-1]\)右邊的元素合併地看做是\(R[i + 2]\),而中間兩個數進行交換,得到的一個等價的陣列\(t={L[i-1],a[i+1],a[i],R[i +2]}\).只需檢查這個陣列是否合法就可以了.

當然,這個做法無法處理邊角情況,也就是交換第一個元素和第二個元素,以及最後一個元素和倒數第二個元素.對於這兩種情況可以直接提前處理.但是這樣提前處理的時候又導致\(n=1\)的情況需要特判,繼續堆一下就行了.

程式碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)	
#define forr(i,x,n)	for(int i = n;i >= x;--i)

const int N = 2e5+7;
const ll INF = 1e18;
int a[N],b[N];
ll L[N],R[N];

bool check(vector<ll> a)
{
	forn(i,1,a.size() - 1)
	{
		if(a[i] < a[i - 1])	return 0;
		a[i] -= a[i - 1];
	}
	return a.back() == 0;
}
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		int n;scanf("%d",&n);
		vector<ll> a(n + 17,0),b(n + 17,0);
		forn(i,1,n)	scanf("%lld",&a[i]),b[i] = a[i];
		if(n == 1)
		{
			puts("NO");
			continue;
		}
		int ok = 0;
		if(check(a))	ok = 1;
		swap(a[1],a[2]);		if(check(a))	ok = 1;swap(a[1],a[2]);
		swap(a[n],a[n - 1]);	if(check(a))	ok = 1;swap(a[n],a[n - 1]);
		
		forn(i,1,n)	L[i] = R[i] = -INF;
		
		forn(i,1,n)	if(b[i] < b[i - 1])	break;else	L[i] = b[i] -= b[i - 1];
		forn(i,1,n)	b[i] = a[i];
		forr(i,1,n)	if(b[i] < b[i + 1])	break;else	R[i] = b[i] -= b[i + 1];
		
		forn(i,2,n - 2)
		{
			vector<ll> t = {L[i - 1],a[i + 1],a[i],R[i + 2]};
			if(L[i - 1] == -INF || R[i + 2] == -INF)	continue;
			if(check(t))	{ok = 1;break;}
		}
		
		if(ok)	puts("YES");
		else 	puts("NO");
    }
    return 0;
}