a simple stone game--k倍動態規劃減法遊戲
要求b[j],表示a[0]……a[i]組成,那麼顯然是要用到a[i]的,不然不就成了b[i-1],既然用了a[i],但是又要使相鄰的倍數在K以上。則找到最大的j,使a[j]*k<a[i]那麼滿足條件,便是a[0]……a[j]能組成的最大的數,加上a[i],那麼後者表示當前項不能和之前項組合,那麼最大的數就只能是本身
附上我當時搜的大神的資料,看到這個我才搞懂的k倍動態規劃的原理
思路:
博弈論中的 K倍動態減法遊戲,難度較大,參看了好多資料才懵懂!
此題可以看作 Fibonacci 博弈的擴充套件,建議沒弄懂 Fibonacci博弈的先學那個,個人整理 http://blog.csdn.net/tbl_123/article/details/24033245 ;
而說擴充套件體現在數列不再是Fib數列,是根據 k 的值自行構造的,其它換湯不換藥,具體構造方法如下:
這兒方便說明白,首先根據k的值分情況討論:
1) 當 k = 1 時,必敗態為 n = 2 ^ i, 因為我們把數按二進位制分解後,拿掉二進位制的最後一個1,那麼對方必然不能拿走倒數第二位的1,因為他不能拿的比你多。你只要按照這個策略對方一直都不可能拿完。所以你就會贏。而當分解的二進位制中只有一個1時,因為第一次先手不能全部取完,所以後手一定有辦法取到最後一個1,所以必敗!
舉個例子,當 n = 6 = (110)時:
第一輪:先手第一次取最右邊的1,即2個,此時還剩4(100)個,後手能取1或2個;
第二輪:假如上輪後手取的兩個,先手再取兩個直接贏了
假如後手取了一個,那麼還剩三個,自己只能去1個,以後也只能取一個,所以必勝!
2) 當 k = 2 時,赤裸裸的Fibonacci博弈了,具體這兒不多說,自己再上述部落格已寫的很明白了。其實n經拆解後也可以表示成二進位制的形式,用 k = 1時的方法來理解,比如 n = 11 = 7 + 3 + 1,可表示成 10101;
3) 當 k 取任意非零正值時,重點來了:
猶如Fibonacci博弈,我們首先要求一個數列,將n分解成數列中一些項的和,然後就可以按Fibonacci博弈的解決方法來完成,也可以按二進位制的方法來理解,每次取掉最後一個1 還是符合上面的條件。
我們用a陣列表示要被求的數列,b陣列中的b[i]儲存 a[0...i] 組合能夠構造的最大數字。這兒有點難理解,所謂構造就是指n分解為Fib數相加的逆過程。舉例說明,當k = 2 時,a[N]={1, 2, 3, 5, 8, 13, 21, 33....} (Fibonacci陣列);那麼b[3] 即 1、2、 3 能夠構造的最大數字,答案是4,有點匪夷所思?或許你會問為什麼不是5、6或者其它的什麼,其實是這樣的 ,4 能分解成 1+3 是沒有爭議的,但5能分解成2+3嗎? 不能,因為5本身也是Fibonacci數;6雖然能分解,但不是分解成1+2+3,而是分解成1+5。
經過上述,我們知道b[i] 是 a[0...i] 能夠構造出的最大數字,那麼a[i +1] = b[i]+1;因為a陣列(Fib陣列)所存的數字都是不可構造的(取到它本身就是必敗態),顯然a[0...i]構造的最大數字 + 1 即為下一個不可構造的數字了(a[i + 1])。
然後關於b[i]的計算,既然是a[0...i]構造最大數字,那麼 a[i]是一定要選用的(這兒需要一定的推理,a[i]構造數字時,相鄰的j個是不能同時用的,就像上述的2、3不能構造出5一樣,推理請自己完成),那麼要選用的下一項只能遞減尋找,直到找到 a[t] 滿足 a[t] * K < a[i] ,而b[t]就是a[0...t]所能構造的最大數字,再加上a[i], 即為a[0...i]能構造的最大數字,於是b[i] = b[t] + a[i]。
求的數列後,之後的工作就簡單了,跟Fibonacci博弈一樣一樣的,如果n=數列中的數,則必敗,否則必勝;必勝時還要求輸出第一步取法,其實就是分解的數列中最小的一個,將見程式碼。
大神的思路就是明白,再次膜拜,其實懂了斐波那契博弈後完全就會理解k倍動態規劃的原理。附上大神程式碼一份:#include
#define N 20000005
int a[N], b[N]; // a 為數列, b 儲存 a[0...i] 能構造出的最大的數
int main()
{
int n, k;
int loop = 0, casei = 1;
scanf("%d",&loop);
while(loop --){
scanf("%d%d",&n,&k);
a[0] = b[0] = 1;
int i = 0, j = 0;
while(n > a[i]){ // 構建數列
i ++;
a[i] = b[i - 1] + 1;
while(a[j + 1] * k < a[i])
j ++;
if(k * a[j] < a[i])
b[i] = b[j] + a[i];
else
b[i] = a[i];
}
printf("Case %d: ", casei ++);
if(n == a[i])
printf("lose\n");
else{
int ans;
while(n){
if(n >= a[i]){ // 構成 n 的最小的數列中的數字,即為第一次要取的數字
n -= a[i];
ans = a[i];
}
i --;
}
printf("%d\n",ans);
}
}
return 0;
}