基本就是把这里的题过了一遍
SG函数资料(入门必备)
感觉很不错的文章
博弈论(一):Nim游戏
博弈论(二):Sprague-Grundy函数
寻找必败态——一类博弈问题的快速解法
练手
hdu1846
#include<cstdio>
using namespace std;
int main(){
int T,n,m;
scanf("%d",&T);
while (T--){
scanf("%d%d",&n,&m);
if (n<=m)puts("first");//多余的一行
else puts((n%(m+1))?"first":"second");
}
return 0;
}
hdu2147
#include<cstdio>
using namespace std;
int main(){
int n,m;
while (scanf("%d%d",&n,&m)==2&&n&&m){
puts((n&m&1)?"What a pity!":"Wonderful!");
}
return 0;
}
hdu2188
#include<cstdio>
using namespace std;
int main(){
int T,n,m;
scanf("%d",&T);
while (T--){
scanf("%d%d",&n,&m);
puts((n%(m+1))?"Grass":"Rabbit");
}
return 0;
}
hdu2148
PN似乎反过来了。。。。找规律
#include<cstdio>
using namespace std;
int main(){
int n,m;
while (scanf("%d%d",&m,&n)==2){
if (n>=m){
for (int i=m;i<n;i++)printf("%d ",i);
printf("%d
",n);
}else {
int x=m%(n+1);
if (x)printf("%d
",x);
else puts("none");
}
}
return 0;
}
对于一个Nim游戏的局面(a1,a2,…,an),它是P-position当且仅当a1^a2^…^an=0,其中^表示异或(xor)运算。
先记个结论(坑以后再填)
hdu1907
#include<cstdio>
using namespace std;
int main(){
int T,n,x;
scanf("%d",&T);
while (T--){
scanf("%d",&n);
int ans=0,cnt=0;
for (int i=1;i<=n;i++){
scanf("%d",&x);
ans ^= x;
if (x>1)cnt++;
}
if (!cnt)ans = !(n&1);
if (ans)puts("John");
else puts("Brother");
}
return 0;
}
hdu2509
#include<cstdio>
#include<iostream>
using namespace std;
int main(){
int n,x;
while (scanf("%d",&n)==1){
int ans=0,cnt=0;
for (;n--;){
scanf("%d",&x);
ans ^= x;
if (x>1) cnt++;
}
if (!cnt)ans = !!(n&1);//这个时候n都=0了,前面循环应该for个i,这里能过是因为数据弱吧
if (ans)puts("Yes");
else puts("No");
}
return 0;
}
hdu1527
威佐夫博弈
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const double phi=(1+sqrt(5))/2.0;
int main(){
int m,n;
while (scanf("%d%d",&n,&m)==2){
if (min(n,m)==(int)(phi*abs(n-m)))puts("0");
else puts("1");
}
return 0;
}
继续练手
hdu1847
#include<cstdio>
using namespace std;
int main(){
int n;
while (scanf("%d",&n)==1){
if (n%3==0)puts("Cici");
else puts("Kiki");
}
return 0;
}
hdu1849
#include<cstdio>
using namespace std;
int main(){
int n,x;
while (scanf("%d",&n)==1&&n){
int ans=0;
for (;n--;){
scanf("%d",&x);
ans ^= x;
}
puts(ans?"Rabbit Win!":"Grass Win!");
}
return 0;
}
hdu1850
#include<cstdio>
using namespace std;
const int N=111;
int a[N];
int main(){
int n;
while (~scanf("%d",&n)&&n){
int nim=0;
for (int i=1;i<=n;i++){
scanf("%d",&a[i]);
nim ^= a[i];
}
if (!nim)puts("0");
else {
int cnt=0;
for (int i=1;i<=n;i++){
int kk = nim^a[i];
if (a[i]>kk)cnt++;
}
printf("%d
",cnt);
}
}
return 0;
}
hdu1848
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1007;
int sg[N],fib[N];
void getFib(){
fib[1] = 1 , fib[2] = 2;
for (int i=3;i<=17;i++)
fib[i]=fib[i-1]+fib[i-2];
}
void getSG(){
getFib();
/*memset(sg,0,sizeof(sg));
int lastP;
for (int i = 0; i < N ; i++ ){
if (sg[i]==-1) sg[i] = i-lastP;
else {//sg[i]==0
lastP = i ;
for (int j=1;j<17&&i+fib[j]<N;j++){
sg[i+fib[j]] = -1 ;
}
}
//printf("sg[%d]=%d
",i,sg[i]);
}*/
int j;
sg[0]=0;
bool mex[17];
for(int i=1;i<N;i++){
memset(mex,0,sizeof(mex));
for (j=1 ;fib[j]<=i ;j++ )mex[sg[i-fib[j]]]=1;
for (j=0 ; j<N ; j++ )if(!mex[j])break;
sg[i] = j;
}
}
//by cww97
int main(){
int x,y,z;
getSG();
while (scanf("%d%d%d",&x,&y,&z)==3){
if ( !x&& !y&& !z ) break;
int ans = sg[x]^sg[y]^sg[z];
if (ans)puts("Fibo");
else puts("Nacci");
}
return 0;
}
hdu1536
再次上演悲剧,调试N弄成了15,然后RTE,然后设成了10W(题目上数字看错了),然后TLE,然后,,,,好气啊
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10007;
int s[110],sg[N],k;
void getSG(){
sg[0]=0;
bool vis[N];
for (int i=1;i<N;i++){
memset(vis,0,sizeof(vis));
for (int j=1;s[j]<=i&&j<=k;j++)vis[sg[i-s[j]]]=1;
for (int j=0;j<N;j++)if(!vis[j]){sg[i]=j;break;}
}
}
int main(){
//freopen("fuck.in","r",stdin);
int m,n,x;
while (scanf("%d",&k)==1&&k){
for (int i=1;i<=k;i++)scanf("%d",&s[i]);
sort(s+1,s+k+1);
getSG();
scanf("%d",&n);
while (n--){
scanf("%d",&m);
int ans=0;
while (m--){
scanf("%d",&x);
ans ^= sg[x];
}
putchar(ans?'W':'L');
}
puts("");
}
return 0;
}
第一次多校训练的题,,就是这题引出了这一整篇文fei章hua
学(zhuang)术(bi)一点的说法是,状态压缩+SG
先说状态压缩
简单点的话,一行一行处理,第i个格子有棋子就 +1<< i(2的i次方) ,最后得到一个数字表示这一行的一种状态,个人习惯从1开始数,,,于是1<<0=1遍没有意义,导致我的表只有偶数(一半是空的,为了个人的强迫症强行多开了一倍空间,不过我喜欢,嘻嘻),所以所有状态的最小值是0(没有棋子),最大是(1<<21-2)(所有格子都有棋子,等比数列求个和就行)。
SG嘛,对棋盘的每个状态求sg值,注意所有棋子集中在右边的时候是没法走的(sg=0),所以从大到小求,,,状态变化,我一开始写的加减号,然后看有人用的位运算,以为自己错了,改成位运算的样子,A了之后又感觉自己想法不错,改回加减号,果然还是A了,,当然,感觉用位运算更易于理(zhuang)解(bi)一点,这里贴的代码就是位运算版本的辣(mdzz,一个符号讲这么多废话)
#include<cstdio>
#include<cstring>
using namespace std;
const int N=(1<<21)+7;
int sg[N];
int c[23],C;//the position of chesses
int next(int x){//move the x-th chess
for (int i=x;i<=C;i++){
if (c[i+1]>c[i]+1)return 1<<(c[i]+1);
}
return 0;
}
void getSG(){
bool vis[107];// enough??
for (int i=N-9;i>=0;i-=2){//downto
C=0; //get the stituation
for (int j=1;j<=20;j++)//num->array
if (i&(1<<j)) c[++C]=j;
c[C+1]=21;
memset(vis,0,sizeof(vis));//sgsgsg
for (int j=1;j<=C;j++){
int nxt=next(j);//move a chess
if (nxt)vis[sg[i^(1<<c[j])^nxt]]=1;//- +
}
for (int j=0;j<107;j++)
if(!vis[j]){sg[i]=j;break;}
}
}
int main(){
//freopen("fuck.in","r",stdin);
int T,n,m,x;
getSG();
scanf("%d",&T);
while (T--){
int ans = 0;
scanf("%d",&n);
for (;n--;){
int che = 0;
scanf("%d",&m);
for (;m--;){
scanf("%d",&x);
che |= 1<<x;//+=
}
ans ^= sg[che];
}
if (ans)puts("YES");
else puts("NO");
}
return 0;
}