1. 程式人生 > >2019.01.02-bzoj-4550: 小奇的博弈

2019.01.02-bzoj-4550: 小奇的博弈

題目描述:

這個遊戲是在一個1*n的棋盤上進行的,棋盤上有k個棋子,一半是黑色,一半是白色。最左邊是白色棋子,最右邊 是黑色棋子,相鄰的棋子顏色不同。 小奇可以移動白色棋子,提比可以移動黑色的棋子,它們每次操作可以移動1到d個棋子。每當移動某一個棋子時, 這個棋子不能跨越兩邊的棋子,當然也不可以出界。當誰不可以操作時,誰就失敗了。小奇和提比輪流操作,現在 小奇先移動,有多少種初始棋子的佈局會使它有必勝策略?

演算法標籤:dp,博弈

總覺得所有博弈問題的轉化都很神奇,可能(肯定)是因為我是菜雞。

思路:

對於每一對黑子與白子,向中間靠攏最優,相當於是有(k/2)堆石子,每次一個人可以選擇不超過d堆取走若干個石子,是經典的NIM演算法。 關於Nim演算法: 基本: n堆石子一人每次在一堆中取若干個,誰先取不到輸,石子每堆個數異或值,為0為先手必敗,其餘為先手必勝。 本題稍拓展:
n堆石子每人挑至多d堆取若干個,將石子進行二進位制拆分,拆分後每一位相加(不進位),若每一位%(d+1)都等於0,則為先手必敗,其餘為先手必勝。 於是考慮本題,對於方案數,顯然考慮先手必敗的情況更容易,於是我們用總方案數-先手必敗方案數,即為答案。 令f[i][j],i表示前i個二進位制位,用了j個單位的方案數。 轉移:列舉每次在當前位加了多少個(d+1)。由於還要列舉這些加一放在哪個數字上,以及每個棋子的初始位置等等,有一些地方要靈活運用組合數。

以下程式碼:

#include<bits/stdc++.h>
#define il inline
#define _(d) while(d(isdigit(ch=getchar())))
using namespace std;
const int N=1e4+5,p=1e9+7;
int n,k,d,c[N][105],f[20][N],ans;
il int read(){int x;char ch;_(!);x=ch^48;_()x=(x<<1)+(x<<3)+(ch^48);return x;}
il int mu(int
x,int y){if(x+y>=p)return x+y-p;return x+y;} int main() { n=read();k=read();d=read(); for(int i=0;i<=n;i++){ c[i][0]=1;for(int j=1;j<=k&&j<=i;j++)c[i][j]=mu(c[i-1][j-1],c[i-1][j]); } f[0][0]=1; for(int i=0;i<16;i++)for(int j=0;j<=n-k;j++)
for(int z=0;(1<<i)*(d+1)*z<=j&&(d+1)*z<=(k>>1);z++){ f[i+1][j]=mu(f[i+1][j],1ll*f[i][j-(1<<i)*(d+1)*z]*c[k>>1][(d+1)*z]%p); } for(int i=0;i<=n-k;i++)ans=mu(ans,1ll*f[16][i]*c[n-i-(k>>1)][k>>1]%p); ans=mu((c[n][k]-ans)%p,p);printf("%d\n",ans); return 0; }
View Code