SDOI 2019 移動金幣 題解

Hypoc_發表於2020-10-08

題目傳送門

題目大意: 一個長度為 n n n 的序列上有 m m m 個金幣,兩個人輪流操作,一個人可以將一個金幣向左移動任意格,但是不能越過別的金幣,問有多少種局面先手必勝。

題解

感覺像這兩題(12)的合體,不過合的也很巧妙。

轉化一下這個博弈:相當於有 m + 1 m+1 m+1 堆石子,每次可以將一堆石子中的若干個移到下一堆內,第 m + 1 m+1 m+1 堆的不可以被操作。

對於第 i i i 堆石子,假如 m + 1 − i m+1-i m+1i 為偶數,那麼這堆石子其實是沒用的,假如 Alice \text{Alice} Alice 將其中若干個移到下一堆,那麼 Bob \text{Bob} Bob 接著將這若干個再移到下一堆,那麼這若干個依然在 m + 1 − i m+1-i m+1i 為偶數的堆內,一直搞下去那麼這些石子最後就會停在第 m + 1 m+1 m+1 堆內,此時依然是 Alice \text{Alice} Alice 操作。

那麼如果將 m + 1 − i m+1-i m+1i 為奇數的堆內的石子移到下一堆,那麼這些石子就變成了沒用的石子了,則 m + 1 − i m+1-i m+1i 為奇數的堆就構成了一個 N i m Nim Nim 遊戲,於是 dp \text{dp} dp 一下就做完了, dp \text{dp} dp 部分和上面連結裡第二題是一樣的,做過的話就不用再看我講一次了。

現在的子問題是 n n n 個石子分成 m m m 堆構成的 N i m Nim Nim 遊戲有多少種情況先手必勝(注意這裡 n , m n,m n,m 和上面的不是同一個),這不太好求,正難則反,考慮必敗的情況數,令 f [ i ] f[i] f[i] 表示 i i i 個石子分 m m m 堆先手必敗的方案數。先手必敗需要滿足:將每堆石子數量轉成二進位制數後,對於任意 j j j,第 j j j 位為 1 1 1 的數的個數為偶數。考慮列舉每一位 1 1 1 的個數,用組合數將他們分配到 m m m 堆內,就可以做 d p dp dp 了,時間複雜度 O ( 17 n m ) O(17nm) O(17nm)

程式碼如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 150010
#define mod 1000000009

int n,m,k;
int Binom[maxn][61];
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
int add(int x){return x>=mod?x-mod:x;}
void BinomInit(){
	Binom[0][0]=1;
	for(int i=1;i<=n;i++){
		Binom[i][0]=1;
		for(int j=1;j<=i&&j<=m;j++){
			Binom[i][j]=add(Binom[i-1][j]+Binom[i-1][j-1]);
		}
	}
}
int f[maxn],tmp[maxn];

int main()
{
	scanf("%d %d",&n,&m);k=(m+1)/2;
	BinomInit();f[0]=1;
	for(int i=0;i<=17;i++){
		int bin=1<<i;
		for(int j=0;j<=k&&j*bin<=n-m;j+=2){
			for(int l=0;l+j*bin<=n-m;l++){
				add(tmp[l+j*bin],1ll*f[l]*Binom[k][j]%mod);
			}
		}
		for(int j=0;j<=n-m;j++)f[j]=tmp[j],tmp[j]=0;
	}
	int ans=0;
	for(int i=0;i<=n-m;i++)add(ans,1ll*f[i]*Binom[n-m-i+(m+1-k-1)][m+1-k-1]%mod);
	ans=(Binom[n-m+(m+1-1)][m+1-1]-ans+mod)%mod;
	printf("%d",ans);
}

相關文章