博弈
hdu 1907 John
s.anti-nim
#include<iostream> #include<stdio.h> using namespace std; int main(){ int T; int N; int A; int i; int sum; int ones;//1的个数 scanf("%d",&T); while(T--){ scanf("%d",&N); sum=0; ones=0; for(i=0;i<N;++i){ scanf("%d",&A); sum^=A; if(A==1){ ++ones; } } if(sum==0){//奇异 if(N-ones==0){//T0 printf("John\n"); } else{//T2 printf("Brother\n"); } } else{//非奇异 if(N-ones==0){//S0 printf("Brother\n"); } else{//S1、S2 printf("John\n"); } } } return 0; }
hdu 2509 Be the Winner
d.我承认题意没读懂。
s.anti-nim
#include<iostream> #include<stdio.h> using namespace std; int main(){ int n; int x; int i; int sum; int ones; while(~scanf("%d",&n)){ sum=0; ones=0; for(i=0;i<n;++i){ scanf("%d",&x); sum^=x; if(x==1){ ++ones; } } if(sum==0){ if(n-ones==0){//T0 printf("Yes\n"); } else{ printf("No\n"); } } else{ if(n-ones==0){//S0 printf("No\n"); } else{ printf("Yes\n"); } } } return 0; }
hdu 1536 S-Nim
题意:首先输入K 表示一个集合的大小 之后输入集合 表示对于这对石子只能去这个集合中的元素的个数
之后输入 一个m 表示接下来对于这个集合要进行m次询问
之后m行 每行输入一个n 表示有n个堆 每堆有n1个石子 问这一行所表示的状态是赢还是输 如果赢输入W否则L
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; //注意 S数组要按从小到大排序 SG函数要初始化为-1 对于每个集合只需初始化1遍 //n是集合s的大小 S[i]是定义的特殊取法规则的数组 int s[110],sg[10010],n; int SG_dfs(int x) { int i; if(sg[x]!=-1) return sg[x]; bool vis[110]; memset(vis,0,sizeof(vis)); for(i=0;i<n;i++) { if(x>=s[i]) { SG_dfs(x-s[i]); vis[sg[x-s[i]]]=1; } } int e; for(i=0;;i++) if(!vis[i]) { e=i; break; } return sg[x]=e; } int main() { int i,m,t,num; while(scanf("%d",&n)&&n) { for(i=0;i<n;i++) scanf("%d",&s[i]); memset(sg,-1,sizeof(sg)); sort(s,s+n); scanf("%d",&m); while(m--) { scanf("%d",&t); int ans=0; while(t--) { scanf("%d",&num); ans^=SG_dfs(num); } if(ans==0) printf("L"); else printf("W"); } printf("\n"); } return 0; }
hdu 1848 Fibonacci again and again
题意:取石子问题,一共有3堆石子,每次只能取斐波那契数个石子,先取完石子者胜利,问先手胜还是后手胜
- 可选步数为一系列不连续的数,用GetSG(计算)
- 最终结果是所有SG值异或的结果
#include<stdio.h> #include<string.h> #define N 1001 //f[]:可以取走的石子个数 //sg[]:0~n的SG函数值 //hash[]:mex{} int f[N],sg[N],hash[N]; void getSG(int n) { int i,j; memset(sg,0,sizeof(sg)); for(i=1;i<=n;i++) { memset(hash,0,sizeof(hash)); for(j=1;f[j]<=i;j++) hash[sg[i-f[j]]]=1; for(j=0;j<=n;j++) //求mes{}中未出现的最小的非负整数 { if(hash[j]==0) { sg[i]=j; break; } } } } int main() { int i,m,n,p; f[0]=f[1]=1; for(i=2;i<=16;i++) f[i]=f[i-1]+f[i-2]; getSG(1000); while(scanf("%d%d%d",&m,&n,&p)!=EOF) { if(m==0&&n==0&&p==0) break; if((sg[m]^sg[n]^sg[p])==0) printf("Nacci\n"); else printf("Fibo\n"); } return 0; }
hdu 1846 Brave Game
d.
1、 本游戏是一个二人游戏;
2、 有一堆石子一共有n个;
3、 两人轮流进行;
4、 每走一步可以取走1…m个石子;
5、 最先取光石子的一方为胜;
s.Bash Game
#include<iostream> #include<stdio.h> using namespace std; int main(){ int C; int n,m; scanf("%d",&C); while(C--){ scanf("%d%d",&n,&m); if(n%(m+1)>0){ printf("first\n"); } else{ printf("second\n"); } } return 0; }
hdu 1847 Good Luck in CET-4 Everybody!
d.
1、 总共n张牌;
2、 双方轮流抓牌;
3、 每人每次抓牌的个数只能是2的幂次(即:1,2,4,8,16…)
4、 抓完牌,胜负结果也出来了:最后抓完牌的人为胜者;
s.
画出PN图,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
N N P N N P N N P N N P N N P N
上图很容易画出,因为剩余1或2张牌时,后者可以一次取光获胜,当剩余3张牌时,因为后者只能取1张或者2张,所以后者必败,一次类推就可以得出上图的PN图。跟据PN图可以很容易的得出当牌的张数为3的倍数时先手必败,反之先手必胜。
ps:好神奇,好像转成只能取1和2的Bash Game了。
#include<iostream> #include<stdio.h> using namespace std; int main(){ int n; while(~scanf("%d",&n)){ if(n%3>0){ printf("Kiki\n"); } else{ printf("Cici\n"); } } return 0; }
c2.没想到的话老实求sg值
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define N 1024 //f[]:可以取走的石子个数 //sg[]:0~n的SG函数值 //_hash[]:mex{} int f[N],sg[N],_hash[N]; void getSG(int n) { int i,j; memset(sg,0,sizeof(sg)); for(i=1;i<=n;i++) { memset(_hash,0,sizeof(_hash)); for(j=1;f[j]<=i;j++) _hash[sg[i-f[j]]]=1; for(j=0;j<=n;j++) //求mes{}中未出现的最小的非负整数 { if(_hash[j]==0) { sg[i]=j; break; } } } } int main(){ int i; f[1]=1; for(i=2;i<N;++i){ f[i]=f[i-1]*2; } getSG(1005); int n; while(~scanf("%d",&n)){ if(sg[n]>0){ printf("Kiki\n"); } else{ printf("Cici\n"); } } return 0; }
hdu 1517 A Multiplication Game
d.两个人玩游戏,给一个数字n,每次操作可以从2~9中任选一个数字,并把它与p相乘,(游戏开始时p=1)
两人轮流操作,当一个人操作完后p>=n,这个人就是胜者。
s.
①、如果输入是2~9,因为Stan是先手,所以Stan必胜。
②、如果输入是10~18(9*2),因为Ollie是后手,不管第一次Stan乘的是多少,Stan肯定在2~9之间,如果Stan乘以2,那么Ollie就乘以9,那么Ollie乘以大于1的数都能超过10~18中的任何一个数,Ollie必胜。
③、如果输入的是19~162(9*2*9),那么这个范围Stan必胜。
④、如果输入是163~324(9*2*9*2),这个是Ollie的必胜范围。
…………
可以发现必胜态是对称的。
如果“我方”首先给出了一个在N不断除18后的得到不足18的数M,“我方”就可以胜利,然而双方都很聪明,所以这样胜负就决定与N了,如果N不断除18后的得到不足18的数M,如果1<M<=9则先手胜利,即Stan wins.如果9<M<=18则后手胜利。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int main(){ //freopen("input.txt","r",stdin); double n; //用long long 就不能AC了,求解。。。。。。。。 while(cin>>n){ while(n>18) n/=18; if(n<=9) printf("Stan wins.\n"); else printf("Ollie wins.\n"); } return 0; }
hdu 1079 Calendar Game
ps:还不懂
d.从当前日期,在他/她转的玩家可以移动到下一个历日或下月的同一天。当在之后的一个月中没有在同一天,播放器只能移动到下一个的日历日期。例如,从1924年12月19日,你可以移动到1924年12月20日,下一个日期,或一月19日,1925年,在同一天在下个月。然而,2001年1月31日,你可以只移动2001年2月1日,因为2001年2月31日是无效的。一个球员赢得比赛时,他/她到底到达的日期2001年11月4日。如果一个玩家移动到日期2001年11月4号之后,他/她输了比赛。
s.
(2001 , 11 , 4)是个必败点,能到(2001, 11 , 4)的是必胜点,由时间从后向前推。
最后若输入的sg[] = 0即为必败点,输出 NO
#include<iostream> #include<string.h> using namespace std; int sg[500][20][40]; int day[400]; int mm[15]={0,31,28,31,30,31,30,31,31,30,31,30,31}; int g(int y , int m , int d) { if(sg[y][m][d]!=-1) return sg[y][m][d]; int flag=0; if(y>101) return sg[y][m][d]=0; if(m==2 && d==day[y] || m<12 && mm[m]==d && mm[m+1]>=d) { int a=g(y,m+1,d) , b=g(y,m+1,1); if(a==0 || b==0) return sg[y][m][d]=1; else if(a && b) return sg[y][m][d]=0; } else if(m<12 && mm[m]==d && mm[m+1]<d) { int a=g(y,m+1,1); if(a==0) return sg[y][m][d]=1; else return sg[y][m][d]=0; } else if(m==2 && d<day[y] || m<12 && d<mm[m] || m==12 && d<mm[12]) { //cout<<"**************"<<endl; int a=g(y,m,d+1) , b=g(y , m+1 , d);// cout<<"a= "<<a<<" b= "<<b<<endl; if(a==0 || b==0 ) return sg[y][m][d]=1; else if(a && b) return sg[y][m][d]=0; } else if(m==12 && d==mm[12]) { int a=g(y+1 ,1 ,1) , b=g(y+1 ,1, d); if(a==0 || b==0) return sg[y][m][d]=1; else if(a && b) return sg[y][m][d]=0; } } int main() { int y,m,d; int t; memset(sg,-1,sizeof(sg)); for(int i=1900;i<=2001;i++) if(i%4==0&&i%100!=0 || i%400==0) day[i-1900+1]=29; else day[i-1900+1]=28; sg[101][11][4]=0; for(int i=5;i<=mm[11];i++) sg[101][11][i]=1; for(int i=1;i<=mm[12];i++) sg[101][12][i]=1; scanf("%d",&t); while(t--) { scanf("%d%d%d",&y,&m,&d); y-=1900; g(y,m,d);// cout<<"**= "<<sg[101][11][4]<<" "<<sg[101][11][3]<<endl; if(sg[y][m][d]==1) printf("YES\n"); else printf("NO\n"); } return 0; }
s2.
找规律,不管是月份加一,还是日期加一,都改变了奇偶性,只有两个特殊日期9月30日,和11月30日例外(不管该年是否为润年,2月28\ 29同样一步都会发生正常奇偶变化)。
那么目标日期是11月4日,为奇数。初始日期如果为偶数的话,先者必胜。
考虑特殊是日期,两个特殊日期本来为奇数,可以做到移动一步还是奇数,那么必胜点与必败点发生变化。
那么会不会在中途经过这两个日期呢。
如果本来为偶数,如果经过特殊日期就会改变奇偶,从必胜到必败。作为先手,不会主动进入特殊日期,而后者不可能从奇数依旧到达特殊日期的奇数。
如果本来为奇数,同样先手想赢,但是不可能进入特殊日期。保持奇偶性交替变化。
这样一来只可能是初始为特殊日期,否则中途不可能出现特殊日期
#include<iostream> #include<string.h> using namespace std; int main() { int y,m,d , t; scanf("%d",&t); while(t--) { scanf("%d%d%d",&y,&m,&d); if((m+d)%2==0 || m==9 && d==30 || m==11 && d==30) printf("YES\n"); else printf("NO\n"); } return 0; }
hdu 1849 Rabbit and Grass
s.nim
#include<stdio.h> int main() { int ans,n,a; while(scanf("%d",&n),n) { ans=0;//因为一个数和0的异或等于本身,所以可以赋初值为0 while(n--) { scanf("%d",&a); ans=ans^a; } if(ans==0) printf("Grass Win!\n"); else printf("Rabbit Win!\n"); } return 0; }
hdu 2149 Public Sale
s.Bash Game
#include<stdio.h> int main() { int n,m; while(scanf("%d%d",&m,&n)!=EOF) { if(m%(n+1)==0) { printf("none\n"); continue; } if(m<=n) { printf("%d",m); for(int i=m+1;i<=n;i++) printf(" %d",i); printf("\n"); continue; } printf("%d\n",m%(n+1)); } return 0; }
hdu 2147 kiki's game
在一个n*m的棋盘内,从(1,m)点出发,每次可以进行的移动是:左移一,下移一,左下移一。然后kiki每次先走,判断kiki时候会赢(对方无路可走的时候)。
我们可以把PN状态的点描绘出来::
#include"stdio.h" int main( ) { int n,m; while(scanf("%d%d",&n,&m)&&(n!=0||m!=0)) { if(n%2==0||m%2==0) printf("Wonderful!\n"); else printf("What a pity!\n"); } return 0; }
hdu 1404 Digital Deletions
一串由0~9组成的数字,可以进行两个操作:1、把其中一个数变为比它小的数;2、把其中一个数字0及其右边的所有数字删除。
两人轮流进行操作,最后把所以数字删除的人获胜,问前者胜还是后者胜。
字符串长度为1-6,前者胜输出Yes,否则输出No.
s.
从小到大记录,将一步能到达必败点P点的都记为必胜点N点(sg为1 的 点),这样每次遇到的第一个sg为0的点就是必败点。
求出所有的sg值即可。
/* HDU 1404 */ #include<stdio.h> #include<math.h> #include<iostream> #include<string.h> using namespace std; const int MAXN=1000000; int sg[MAXN]; int get_length(int n)//得到整数n的位数 { if(n/100000) return 6; if(n/10000) return 5; if(n/1000) return 4; if(n/100) return 3; if(n/10) return 2; return 1; } void _extend(int n)//sg[n]=0;表示n为后者必胜 //那么所以可以一步变成n的都是前者必胜 { int len=get_length(n); int i; for(i=len;i>=1;i--)//每一个位上加上一个数 { int m=n; int base=1; for(int j=1;j<i;j++) base*=10; int tmp=(m%(base*10))/base; for(int j=tmp;j<9;j++) { m+=base; sg[m]=1;//m为前者必胜点 } } if(len!=6)//长度小于6,则可以在后面加0开头的 { int m=n; int base=1; for(int i=len;i<6;i++) { m*=10; for(int b=0;b<base;b++) sg[m+b]=1; base*=10; } } } void fun() { memset(sg,0,sizeof(sg)); sg[0]=1; for(int i=1;i<MAXN;i++) { if(!sg[i]) _extend(i); } } int main() { char str[8]; int n; fun(); while(scanf("%s",&str)!=EOF) { if(str[0]=='0') //第一个数字是0,则前者必胜 { printf("Yes\n"); continue; } int len=strlen(str);//第一个数字非0,再转化成整型数 n=0; for(int i=0;i<len;i++) { n*=10; n+=str[i]-'0'; } if(sg[n]) printf("Yes\n"); else printf("No\n"); } return 0; }