Codeforces Round #658 (Div. 2)(BCD)
題目連結:https://codeforces.com/contest/1382
B. Sequential Nim(博弈)
C.Prefix Flip (思維題)
D. Unmerge(揹包DP)
B. Sequential Nim
題目大意:給你n堆石子,每堆$a_i$個,你必須按順序從左到右取,每次取至少一個,最多$a_i$個,問先手勝利還是後手。$n\leq 10^5 a_i\leq 10^9$
Example input7 3 2 5 4 8 1 1 1 1 1 1 1 1 6 1 2 3 4 5 6 6 1 1 2 1 2 2 1 1000000000 5 1 2 2 1 1 3 1 1 1output
First Second Second First First Second First
emmm,我們先不考慮1的情況,那麼先手在最後一堆石子前必然拿走$a_i-1$個石子,然後先手永遠是先手,所以先手必勝。
那麼如果有一個1呢?我們考慮1出現的位置,有兩種情況,1在最前面,這個時候,先手被迫拿走1,那麼先手和後手的順序就會被逆轉,先手必敗。如果1不在最前面,那麼先手一定可以使得在1的前面拿走$a_i$個石子,使得後手被迫拿走1,那麼先手還是先手,依然是必勝。
那麼接下來似乎就不需要考慮了,通過對1個1的觀察我們就可以得出結論了,我們只需要考慮字首1的個數就行了。
以下是AC程式碼:
#include <bits/stdc++.h> usingView Codenamespace std; const int mac=1e5+10; int a[mac]; int main(int argc, char const *argv[]) { int t; scanf ("%d",&t); while (t--){ int n; scanf ("%d",&n); for (int i=1; i<=n; i++) scanf ("%d",&a[i]); int f=1,s=0,cnt=0; for (inti=1; i<=n; i++){ if (a[i]==1) cnt++; else break; } if (cnt&1){ if (cnt==n) printf("First\n"); else printf("Second\n"); } else{ if (cnt==n) printf("Second\n"); else printf("First\n"); } } return 0; }
C.Prefix Flip
題目大意:給你2個長度為$n$的01串,你需要將第一個01串通過變換變成第二個01串。你可以進行的變換為,選擇一個字首長度,然後將其元素翻轉,然後再翻轉其位置。比如說001001,選擇長度為3的字首,那麼會得到011001。問你s1可以通過幾步得到s2,並輸出每次選擇的字首長度。$|S|\leq 10^5$,步數必須在$2n$以內
Example input5 2 01 10 5 01011 11100 2 01 01 10 0110011011 1000110100 1 0 1output
3 1 2 1 6 5 2 5 3 1 2 0 9 4 1 2 10 4 1 2 1 5 1 1
此題最為麻煩的就是要考慮位置的翻轉,這樣翻轉之後整個過程就變得非常不可控。所以我們想如何將這個位置翻轉從考慮的因素中剔除掉。我們能想到,對於幾個相同的元素進行翻轉,他們的位置翻轉是沒有意義的。那麼我們就可以考慮直接將串s1全部變成相同的元素,然後再將s1變為s2。這個過程所需要的步數一定是在$2n$以內的。
我們從前往後跑,然後相鄰的兩個元素不一樣,那麼我們就將前面的元素全部翻轉,那麼就可以得到了該位置下字首的所有元素都一樣了。那麼最多執行n-1次。
接下來從後往前跑,如果s1和s2的該位置不一樣,我們直接將該位置的字首翻轉就可以了。最多執行n次。
以下是AC程式碼:
#include <bits/stdc++.h> using namespace std; const int mac=1e5+10; const int inf=1e9+10; char a[mac],b[mac]; int main(int argc, char const *argv[]) { int t; scanf ("%d",&t); while (t--){ int n; scanf ("%d",&n); scanf ("%s",a+1); scanf ("%s",b+1); vector<int>ans; for (int i=2; i<=n; i++){ if (a[i]!=a[i-1]) ans.push_back(i-1); } char last=a[n]; for (int i=n; i>=1; i--){ if (b[i]!=last){ ans.push_back(i); last=last=='0'?'1':'0'; } } printf("%d ",ans.size()); for (int i=0; i<ans.size(); i++) printf("%d ",ans[i]); printf("\n"); } return 0; }View Code
D. Unmerge
題目大意:有兩個長度為n的集合,進行merge操作,每次取兩個集合最前面的最小值,那麼可以得到一個新的長度為2n的集合,現在給你這個長度為2n的集合,問你是否存在著2個長度為n的集合通過merge操作得到它。$n<leq 2000$
Example input6 2 2 3 1 4 2 3 1 2 4 4 3 2 6 1 5 7 8 4 3 1 2 3 4 5 6 4 6 1 3 7 4 5 8 2 6 4 3 2 5 1 11 9 12 8 6 10 7output
YES NO YES YES NO NO
emmm,我們將這個序列進行分段,找到一個數後面第一個比他大的元素進行分段,比如2 3 1 4可以分為2| 3 1| 4。然後跑對每段的長度跑01揹包就可以了,最後判斷一下揹包的價值是否為n。
至於為什麼這樣分的。。。我們可以觀察,對於兩個集合(我們可以看做是一個棧),每次出來的都是棧頂最小的元素,那麼如果先出來了個17,那麼後面出來的元素,如果比17來得小,那麼一定是和17同棧的,因為,如果不是同棧的話出來的就是將17踢掉的那個元素,它肯定會大於17的,所以我們可以這樣來分段。
以下是AC程式碼:
#include <bits/stdc++.h> using namespace std; const int mac=4e3+10; int len[mac],a[mac],dp[mac]; int main(int argc, char const *argv[]) { int t; scanf ("%d",&t); while (t--){ int n; scanf ("%d",&n); for (int i=1; i<=2*n; i++) scanf ("%d",&a[i]); int cnt=1,mx=a[1],num=1; len[cnt]=1; for (int i=2; i<=2*n; i++){ if (a[i]>mx) len[cnt++]=num,num=1,mx=a[i]; else num++; } for (int i=0; i<=2*n; i++) dp[i]=0; for (int i=1; i<cnt; i++) for (int j=n; j>=len[i]; j--) dp[j]=max(dp[j],dp[j-len[i]]+len[i]); if (dp[n]==n) printf("YES\n"); else printf("NO\n"); } return 0; }View Code