1. 程式人生 > >題解報告:hdu 1847 Good Luck in CET-4 Everybody!(入門SG值)

題解報告:hdu 1847 Good Luck in CET-4 Everybody!(入門SG值)

思路 無環 鏈接 得到 put clas 超過 cout HP

題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=1847

Problem Description 大學英語四級考試就要來臨了,你是不是在緊張的復習?也許緊張得連短學期的ACM都沒工夫練習了,反正我知道的Kiki和Cici都是如此。當然,作為在考場浸潤了十幾載的當代大學生,Kiki和Cici更懂得考前的放松,所謂“張弛有道”就是這個意思。這不,Kiki和Cici在每天晚上休息之前都要玩一會兒撲克牌以放松神經。“升級”?“雙扣”?“紅五”?還是“鬥地主”?當然都不是!那多俗啊~
作為計算機學院的學生,Kiki和Cici打牌的時候可沒忘記專業,她們打牌的規則是這樣的:
1、 總共n張牌;
2、 雙方輪流抓牌;
3、 每人每次抓牌的個數只能是2的冪次(即:1,2,4,8,16…)
4、 抓完牌,勝負結果也出來了:最後抓完牌的人為勝者;
假設Kiki和Cici都是足夠聰明(其實不用假設,哪有不聰明的學生~),並且每次都是Kiki先抓牌,請問誰能贏呢?
當然,打牌無論誰贏都問題不大,重要的是馬上到來的CET-4能有好的狀態。
Good luck in CET-4 everybody! Input 輸入數據包含多個測試用例,每個測試用例占一行,包含一個整數n(1<=n<=1000)。 Output 如果Kiki能贏的話,請輸出“Kiki”,否則請輸出“Cici”,每個實例的輸出占一行。 Sample Input 1 3 Sample Output Kiki Cici 解題思路:找找規律,先
舉幾個栗子: 當n=1時,先手必贏; 當n=2時,先手必贏; 當n=3時,無論先手抓多少張牌,後手必贏; 當n=4時,只要先手抓1張牌,接下來就轉化成n=3這個局面,即先手必贏; 當n=5時,只要先手抓2張牌,接下來就轉化成n=3這個局面,即先手必贏; 當n=6時,①當先手抓1張牌時,接下來就轉化成n=5這個局面,即後手必贏;②當先手抓2張牌時,後手可以一次性抓走剩下的4張牌,即後手必贏;③當先手抓4張牌時,後手同樣可以一次性取完剩下的2張牌,即後手必贏;所以無論先手抓多少張牌,後手必贏; 當n=7時,只要先手抓走1張牌,接下來就轉化成n=6這個局,即先手必贏; ...... 再多舉幾個栗子,我們可以發現只要n是3的倍數,則後手必贏;反之,先手必贏,因此可以用以下簡單代碼水過:
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int main()
 4 {
 5     int n;
 6     while(cin>>n){
 7         if(n%3)cout<<"Kiki"<<endl;//不是3的倍數,先手必贏
 8         else cout<<"Cici"<<endl;//是3的倍數,後手必贏
 9     }
10     return 0;
11 }

這題還可以用SG值解決,所謂的SG值就是記錄當前狀態是N是P的具體值,N-position表示必贏狀態(其SG值不為0),P-position表示必輸狀態(其SG值為0)。下面介紹怎麽求SG值:

首先定義mex(minimal excludant)運算,這是施加於一個集合的運算,表示不屬於mex這個集合的最小非負整數。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。

對於一個給定的有向無環圖,定義關於圖的每個頂點的Sprague-Grundy函數g如下:g(n)=mex{ g(m) | m是n的後繼 },這裏的g(n)即sg[n]

拿本題的栗子來講:首先有sg[0]=0,f[]={1,2,4...};(f數組存放可以抓走撲克牌的張數,並且按升序存放)

當n=1時,先手可以抓走1-f{1}張牌,剩余{0}張,mex{sg[0]}={0},故sg[1]=1;

當n=2時,先手可以抓走2-f{1,2}張牌,剩余{1,0}張,mex{sg[1],sg[0]}={1,0},故sg[2]=2

當n=3時,先手可以抓走3-f{1,2}張牌,剩余{2,1}張,mex{sg[2],sg[1]}={2,1},故sg[3]=0;

當n=4時,先手可以抓走4-f{1,2,4}張牌,剩余{3,2,0}張,mex{sg[3],sg[2],sg[0]}={0,2,0},故sg[4]=1;

當n=5時,先手可以抓走5-f{1,2,4}張牌,剩余{4,3,1}張,mex{sg[4],sg[3],sg[1]}={1,0,1},故sg[5]=2

以此類推.....

n 0 1 2 3 4 5 6 7 8 9....

sg[n] 0 1 2 0 1 2 0 1 2 0....

由上述實例我們就可以得到1~n的SG值的計算步驟,如下所示:
①、使用f數組保存可抓取的撲克牌張數。
②、然後使用vis數組來標記當前狀態n的後繼m狀態。
③、最後模擬mex運算,也就是我們在集合mex中查找未被標記值的最小值,將其賦值給sg(n)。
④、不斷的重復 ② - ③ 的步驟,即可完成計算1~n的SG值。

關於3種SG值計算方法(重點):

1、可選步數為1~m的連續整數,直接取模即可,SG(x) = x % (m+1);
2、可選步數為任意步,SG(x) = x;
3、可選步數為一系列不連續的數,用get_SG()計算

此題就是選取第3種方法來計算SG值。

AC代碼:
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn = 1010;
 4 int n,f[11],sg[maxn];
 5 bool vis[maxn];
 6 //f[]:每次抓牌的個數
 7 //sg[]: 0~n的SG函數值
 8 //vis[]:mex{}
 9 void init(){//初始化
10     f[1] = 1;//下標從1開始
11     for(int i=2;i<=10;++i)f[i]=f[i-1]*2;//這裏只需枚舉到512即可,因為1024已經超過n=1000了
12 }
13 void get_SG(){
14     memset(sg,0,sizeof(sg));
15     for(int i=1;i<maxn;++i){
16         memset(vis,false,sizeof(vis));//每輪到當前i就重新初始化vis都為未訪問狀態,找出不屬於這個集合的最小非負整數
17         for(int j=1;j<11 && f[j]<=i;++j)//j<11要放在判斷條件的前面,不然會出現錯誤即越界,因為數組長度只有10
18             vis[sg[i-f[j]]]=true;//i-f[j]為後繼狀態,vis[sg[i-f[j]]]收錄mex集合
19         for(int j=0;j<maxn;++j)//求沒有出現在mex集合中的非負最小值
20             if(!vis[j]){sg[i]=j;break;}
21     }
22 }
23 int main()
24 {
25     init();
26     get_SG();
27     while(cin>>n){
28         if(sg[n])cout<<"Kiki"<<endl;//當sg[n]不為0時,即為N-position,此時先手必贏
29         else cout<<"Cici"<<endl;
30     }
31     return 0;
32 }

題解報告:hdu 1847 Good Luck in CET-4 Everybody!(入門SG值)