1. 程式人生 > 其它 >[洛谷P5363][題解][SDOI2019]移動金幣

[洛谷P5363][題解][SDOI2019]移動金幣

事實上如果不事先了解,還是挺難獨立看出這題的結論的。

容易發現本題的操作就是將左邊的一段空格切出來給右邊。我們抽象一下就是若干堆石子,每次可以從某一堆拿出若干顆到其右側鄰堆。這種階梯 nim 有一個好看的結論:直接對從右數的偶數堆玩普通 nim 即可。因為對奇數堆操作後對手一定可以馬上將其移到下一個奇數堆,這樣對奇數堆的操作就都沒用;而對偶數堆的操作就是將若干顆石子變成沒用的奇數堆石子,這樣就等價於普通 nim。

所以原題就是求將 \(n-m\) 個石子分成 \(m+1\) 組,使得偶數堆的石子個數異或和等於零。

我們可以設 \(f_{i,j}\) 表示放了前 \(i\) 位,當前和為 \(j\)

的方案數,根據偶數堆的每一位都有偶數個 \(1\) 的性質轉移。

const int N=150010,M=60,p=1e9+9;
int n,m,C[N][M],f[20][N],cnt[M];
int main(){
  Read(n),Read(m),C[0][0]=1;
  for(int i=1;i<=n;i++){
    C[i][0]=1;
    for(int j=1;j<=min(i,m);j++){
      C[i][j]=(C[i-1][j]+C[i-1][j-1])%p;
    }
  }
  int ans=C[n][m];m++,n=n-m+1,f[18][0]=1;
  for(int i=0;i<=m;i++){
    for(int j=0;j<=i;j+=2){
      (cnt[i]+=(LL)C[m/2][j]*C[m-m/2][i-j]%p)%=p;
    }
  }
  for(int i=18;i;i--){
    for(int j=0;j<=n;j++){
      if(!f[i][j])continue;
      for(int k=0;k<=m;k++){
        if(j+k*(1<<i-1)>n)continue;
        (f[i-1][j+k*(1<<i-1)]+=(LL)f[i][j]*cnt[k]%p)%=p;
      }
    }
  }
  printf("%d\n",(ans-f[0][n]+p)%p);
  KafuuChino HotoKokoa
}
內容來自_ajthreac_的部落格(https://www.cnblogs.com/juruoajh/),未經允許,不得轉載。