題解報告:hdu 1847 Good Luck in CET-4 Everybody!(入門SG值)
題目鏈接: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 解題思路:找找規律,先
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值:
對於一個給定的有向無環圖,定義關於圖的每個頂點的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值)