POJ 3922A Simple Stone Game


題目鏈接 A Sample Stone Game

  題目大意:給定n,k,表示最初時有n個石頭,兩個人玩取石子游戲,第一個人第一次可以取1~n-1個石頭,后面每個人最多可以拿走前面一個人拿走的個數的K倍,當有一個人可以一次性全部拿走時獲勝。問兩人都在不失誤的情況下,先拿着有沒有必勝局勢。有的話求他第一次最少該取多少個。

思考過程:

首先討論k=1的情況,我們可以把一個數n(石子的個數),寫為二進制下的表示,那先者取走最后一個1,那后者必然不能取走比它高一位的1,那么先拿者一定會贏,當然如果n本來就是2^i形式,那么先拿着當然不能拿走最后一個1,這樣的話,石子就會取完了,所以這時先取者必輸。舉個例子來說 N = 20,他的二進制表示就是

1、10100              先拿者A拿走最后一個1(相當於拿走4個)n變為

2、10000              這時后取者B只能拿m=1,2,3,4個,假如是拿走3個,那么n變為

3、01101              這樣A又可以取走最后一個1,。  

   ... ...                 這樣繼續下去,當這個數n變為1時

   00001,            最后一個1一定還是A拿走的,所以A必贏

而其實第2步中,不管B拿走多少個(即不管m是多少m = 1 or 2 or 3 or 4),在她之后的那個人又一定可以取走最后一個1(即取走x = (1<<y)),且這個數x一定小於等於m(定理1),這樣的話B永遠也不可能取完。

  而如果最初,n就等於2^j,二進制就是           10000...               那A自然不可能取走最后一個1(那就取了n個了),這樣的話A取走一個m剩下的數就轉化為上面,第1步中的形式了,那B一定就是必贏了

 

然后是k=2時定理2可以知道任何一個非菲波拉契數n都可以寫為多個兩兩不相鄰的斐波拉契數的和,這樣的話先取者A先拿走一個小的斐波拉契數x,那后取者就無法取到另一個較大的菲波拉契數y(因為x與y不相鄰,所以y>2*x)。這樣又給A留下了一個非斐波拉契數,一直遞歸下去,A一定就是贏者。

  但同時,如果A最初面臨的就是一個菲波拉契數Fn,但是Fn只能寫為Fn-1 + Fn-2,如果取Fn-2,由於Fn-1 < 2 * Fn-2,所以B可以取完,如果取其他任意一個非斐波拉契數m,B得到的那剩下的數Fn-m就相當於上面的A最初面臨的n,也就是說B必贏。

 

而對於一般的K,由上面的思路,我們的目的就是要讓n寫為一些數的和n1 + n2 + ...+np,而且它們兩兩相鄰之間的倍數關系應該大於k,這樣的話我們先者A取走小一點的數n1,后者B必然不能取走n2,就給A留下了一些數,這樣的話B自然不能取完所以A獲勝。

  在解題時,那我們就應該求出某個數列a[],這個數列要滿足任何一個不再這個數列里面的數都可以用這個數列里的(滿足前提條件前一項的k倍小於后一項的)多項的和表示(就好似定理2中任何一個數都可以寫為多個個不連續的菲波拉契數(不連續就是相當於前一個的k=2倍小於后一個數)的和是同樣的原理)。

  而為了要求出這個數組a,我們引入另一個數組b[],b[i]表示a[0],a[1]...a[i]可以找到兩項a[x]和a[y]滿足a[x] * k < a[y],且可以構造出的數(即a[x]+a[y])的最大值。

  假設a的前i項已經求出,由於前i項最大已經可以構造出b[i],那么b[i]+1就無法被構造出來,所以需要另外開一項a[i+1] = b[i]+1。這時我們要求b[i+1]由於b[i+1]表示的是前i+1個a可以構造出來的最大的數所以一定得用到a[i+1],同時又要滿足必須存在一項a[x]使得a[x]*k<a[i+1],所以我們就從之前的a中找出這個最大的a[x],那么b[i+1]=a[x]+a[i+1]。但是有可能之前數據太少,找不到a[x] * k < a[i+1];那也就是相當於a[i+1]無法構造出來,因此b[i+1] = a[i+1](例如i=0,a[x]只能取a[0],而a[0]*k>=a[1],那么b[1]只能等於a[1])。 提煉出來就是:

if(a[x]*k<a[i+1])

  b[i+1] = a[x] + a[i+1];

else

  b[i+1] = a[i+1];

  然后就是要解決第一步取得最少且獲勝的數量,那么先從n里減去小於n的最大的a[],然后又遞歸下去求這個剩下的n里最大的a[],找到n==a[]的a[]。也就是說這是這一串和里面最小的一個a了,將他輸出就行了

 

 

定理1對任意一個不可以寫為2^k(k>=1)的數n,從它的二進制表達式中先取走最后一個1(即拿走m_A = (1<<k1)),得到一個數n1,那么再從n1中取走m_B(m_B<=m_A)個,得到n2。那么n2的二進制中最后一位1(第k2位)所在的位置所表示的數一定小於等於m_B((1<<k2) <= m_B)。

證明:我們可以把m_B寫為二進制形式,而它的二進制中所有的1所在的位置我們記為a1,a2,a3....ak(k>=1)(a1<a2<a3<...<ak),也就是說m_B = (1<<a1)+(1<<a2)+...+(1<<ak)。那我們用n1 - m_B得到n2,由於在n1中a1位置是0,a1以下的所有位置都是0,而m_B中a1位置是1,m_B在a1以下位置也都是0,所以相減后得到的n2在a1位置一定是1。用二進制表示就是:

n             100...100...100000...          拿走最后一個1(m_A = (1<<k1)),得到

n1            100...100...000000...          再隨便找一個m_B<=m_A

m_B           000...000...010110...          加粗部分從左至右為ak,ak-1...a2,a1,然后用n1 - m_B得到

n2            100...011...101010...          n2在m_B最后一個1的位置(藍色位置)也必定為1

 

定理2:任何一個非斐波拉契數都可以寫為若干個不相鄰的菲波拉契數的和,任何一個菲波拉契數都不能寫為兩個非相鄰的斐波拉契數的和(原因很明顯,因為Fn = Fn-1 + Fn-2,Fn-1與Fn-1相鄰)

上面的思想過程轉自神牛http://hi.baidu.com/lccycc_acm/item/a6f0dd0ec5c44a39f3eafcd3

有了上面的思想,代碼實現就很簡單了

 1 #include <stdio.h>
 2 
 3 int a[2000000],b[2000000];
 4 int main()
 5 {
 6     int n,k;
 7     int T = 0,Case = 1;
 8     while(~scanf("%d",&T))while(T--)
 9     {
10         scanf("%d%d",&n,&k);
11         a[0] = b[0] = 1;
12         int i=0,j=0;
13         while(n > a[i])
14         {
15             i++;
16             a[i] = b[i-1] + 1;
17             while(a[j+1] * k < a[i])
18             {
19                 j++;
20             }
21             if(k * a[j] < a[i])
22             {
23                 b[i] = b[j] + a[i];
24             }
25             else b[i] = a[i];
26         }
27         printf("Case %d: ",Case++);
28         if(n == a[i])printf("lose\n");
29         else
30         {
31             int ans ;
32             while(n)
33             {
34                 if(n >= a[i])
35                 {
36                     n -= a[i];
37                     ans = a[i];
38                 }
39                 i --;
40             }
41             printf("%d\n",ans);
42         }
43 
44     }
45     return 0;
46 }

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM