1. 程式人生 > 實用技巧 >Codeforces Round #658 (Div. 2)(BCD)

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 input
7
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 1
output
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>
using
namespace 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 (int
i=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; }
View Code

C.Prefix Flip

題目大意:給你2個長度為$n$的01串,你需要將第一個01串通過變換變成第二個01串。你可以進行的變換為,選擇一個字首長度,然後將其元素翻轉,然後再翻轉其位置。比如說001001,選擇長度為3的字首,那麼會得到011001。問你s1可以通過幾步得到s2,並輸出每次選擇的字首長度。$|S|\leq 10^5$,步數必須在$2n$以內

Example input
5
2
01
10
5
01011
11100
2
01
01
10
0110011011
1000110100
1
0
1
output
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 input
6
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 7
output
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