这次省赛我们队就悲剧在一道非常脑残的博弈题上了,主要责任就在我
,想搞好博弈任务艰巨呀
http://poj.org/problem?id=3710
论文原题:
1,去环操作, 对于长度为奇数的环sg的值为1,否则为0;
2,然后就是树的删边游戏:叶子节点的sg值为0,中间节点的sg值为其 后继节点的sg+1的异或和
参考的 http://www.cnblogs.com/staginner/archive/2012/03/09/2387839.html 对于环的处理,用了low[],dfn[] 时间复查度 O(100*500)
http://poj.org/problem?id=1704
博弈感觉好难想呀!感谢 http://www.cnblogs.com/AndreMouche/archive/2011/03/27/1996762.html 提供证明
把n个棋子分成两组,例如 [ai,ai+1] , [ a[i+2] , a[i+3] ] ,ai+1 和 ai+2 之间的距离是不影响结果的,a[i+2] 向左移动k步,那么ai+3也向左移动k步,那么[ a[i+2] , a[i+3] ] 是不变的。
举个例子吧:
2 5 7 由于 (2-0-1)^(7-5-1)==0 必败
如果first 移动 2 -> 1 那么 second 移动 1 -> 0
如果first 移动 5 -> 3 那么 second 移动 7 ->5
这样总能保证每次轮流之后 sg==0 必败
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
int chess[1010];
int cmp(int a,int b)
{
return a>b;
}
int main(int argc, char *argv[])
{
int ca,n,a;
scanf("%d",&ca);
while(ca--)
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&chess[i]);
int ans=0;
sort(chess,chess+n,cmp);
chess[n]=0;
for(int i=0;i<n;i+=2)
ans^=(chess[i]-chess[i+1]-1);
puts(ans==0?"Bob will win":"Georgia will win");
}
// system("PAUSE");
return EXIT_SUCCESS;
}
A Funny Stone Game http://blog.sina.com.cn/s/blog_51cea4040100gzdx.html
感谢以上的博客给我提供思路,写的很详细,貌似我理解的还是不是很透测,对于每堆石子,如果先手每次都取的是标号为奇数数的石子,那么先手肯定是是最后取完石子的;
每次只取1个,每堆在奇数-> 偶数->奇数之间 变化
以上博客的代码:
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=25;
int sg[maxn],stone[maxn];
int Case,n;
bool vis[maxn];
int Get_sg(int k)
{
memset(vis,0,sizeof(vis));
for(int i=0;i<k;i++)
for(int j=i;j<k;j++)
{
int p=(sg[i]^sg[j]);
vis[p]=1;
}
for(int i=0;;i++) if(!vis[i]) return i;
}
void init()
{
sg[0]=0;
for(int i=1;i<23;i++)
sg[i]=Get_sg(i);
}
void work(int now)
{
Case++;
for(int i=0;i<n;i++)
if(stone[i])
for(int j=i+1;j<n;j++)
for(int k=j;k<n;k++)
if ((((now^sg[n-i-1])^sg[n-j-1])^sg[n-k-1])==0)
{
printf("Game %d: %d %d %d\n",Case,i,j,k);
return;
}
printf("Game %d: -1 -1 -1\n",Case);
}
int main(int argc, char *argv[])
{
init();
// for(int i=0;i<23;i++)
// cout<<sg[i]<<endl;
while(scanf("%d",&n)==1&&n)
{
for(int i=0;i<n;i++) scanf("%d",&stone[i]);
int now=0;
for(int i=0;i<n;i++)
if(stone[i]&1) now=(now^sg[n-i-1]);
work(now);
}
//system("PAUSE");
return EXIT_SUCCESS;
}
http://blog.sina.com.cn/s/blog_51cea4040100tj9u.html
跟着上面博客的大神练习博弈,上面的题目都很好!博主已经解释的已经很好,这里我就加上点自己的想法;
1, 首先要明白阶梯博弈的模型; 阶梯博弈就是在把偶数阶上的石子拿一部分到奇数阶,或者反过来;如果先手小取的胜利,先手必须要使他操作完后所有的奇数阶的游戏和(即是sg的异或和)== 0 ,应为最后一步肯定是第一阶(奇数阶)向第0阶(偶数阶)移动石子;
2. 在能够判断先手是否必胜之后,在做进一步的探究:先手第一次操作有多少种不同方案必胜?nim=sg1^sg2^sg3.....,(sgi为奇数阶的sg的值)
1) 如果是奇数阶梯的石子个数,假设为size,如果(size^nim) <= size 就 ans++ ;
2)如果是偶数阶,他的石子只能向他的下一个奇数阶移动石子,设他的下一个奇数阶移动石子的sg值为size1,如果 (nim^size1) > size1 && (nim^size1 - size1) <= size 则ans++;
http://acm.hdu.edu.cn/showproblem.php?pid=1524
就是NIM游戏的变形 代码就不贴了
注意在就sg值的时候如果用的是记忆化搜索,要把vis[]数组开成是局部变量,应用记忆花用的递归,我开始就是卡在这了,找了变天
http://acm.hdu.edu.cn/showproblem.php?pid=1848
改变了NIM游戏的每次取的个数不是任意个,而必须是fibo中的每个数,记忆化搜索下即可;
#include <cstdlib>
#include <iostream>
using namespace std;
const int maxn=1010;
int fibo[30],sg[maxn];
void get_sg(int tot)
{
bool vis[maxn]={0};
for(int i=2;i<18&&fibo[i]<=tot;i++)
vis[sg[tot-fibo[i]]]=1;
for(int i=0;;i++)
if(!vis[i]) { sg[tot]=i;break; }
}
int main(int argc, char *argv[])
{
int p,n,m;
fibo[1]=fibo[2]=1;
for(int i=3;;i++)
{
fibo[i]=fibo[i-2]+fibo[i-1];
if(fibo[i]>1000) break;
}
sg[0]=0;
for(int i=1;i<=1000;i++)
get_sg(i);
while(cin>>n>>m>>p&&(n+m+p))
{
int ans=sg[n]^sg[m]^sg[p];
puts(ans?"Fibo":"Nacci");
}
// system("PAUSE");
return EXIT_SUCCESS;
}
http://acm.hdu.edu.cn/showproblem.php?pid=3800
acm_diy的题,开始做的时候没有看到过的人才5呀,果然是神题,思路肯定没有错,但是实现起来巨复杂,通过打标找规律发现x的sg函数值就是x二进制位数,对于< 2^63的数其sg <=63 ,对于每段sg值相同的区间就构建一次矩阵,状态表示为 dp[len][ state ] (state<=63) 表示长度为1--len其异或值为state的个数 还有对于已经是亮的 还有不确定的,所构建的矩阵式不一样的 ,以后再debug把!就敲到这了
#include <cstdlib>
#include <iostream>
using namespace std;
const int mod=100000007;
typedef long long ll;
long long matrix[70][70];
long long ans[70][70];
void f(ll a[70][70],ll b[70][70],ll c[70][70])
{
for(int i=0;i<=64;i++)
for(int j=0;j<=64;j++)
{
c[i][j]=0;
for(int k=0;k<=64;k++)
c[i][j]=(c[i][j]+a[i][k]*b[k][j])%mod;
}
}
void pow(ll a[70][70],ll b[70][70],ll n)
{
ll c[70][70],cc=0;
while(n&&cc<70)
{
cc++;
if(n&1)
{
f(a,b,c);
memcpy(a,c,sizeof(c));
}
n>>=1;
f(b,b,c);
memcpy(b,c,sizeof(c));
}
}
struct Line
{
ll l,r;
}line[10010];
int cmp(Line a,Line b)
{
if(a.l!=b.l) return a.l < b.l;
return a.r < b.r;
}
int main(int argc, char *argv[])
{
long long n,m,cnt[70],ca,l,r;
scanf("%lld",&ca);
while(ca--)
{
scanf("%lld %lld",&n,&m);
memset(ans,0,sizeof(ans));
memset(cnt,0,sizeof(cnt));
for(int i=0;i<m;i++)
scanf("%lld %lld",&line[i].l,&line[i].r);
sort(line,line+m,cmp);
for(int i=0;i<m;i++)
{
l=line[i].l,r=line[i].r;
if(i!=0&&l<=line[i-1].r) l=line[i-1].r+1;
if(l>r) continue;
ll tt=0;
while((1<<(tt+1))-1<l) tt++;
if(r<=(1<<(tt+1))-1) cnt[tt+1]+=r-l+1;
else
{
cnt[tt+1]+=(1<<(tt+1))-l;
tt++;
while(1)
{
if((1<<(tt+1))-1<r) cnt[tt+1]+=(1<<tt),tt++;
else
{
cnt[tt+1]+=r-(1<<tt)+1;break;
}
}
}
}
ans[0][0]=1;
ll dd;
for(dd=1;;dd++)
{
memset(matrix,0,sizeof(matrix));
for(int i=0;i<=63;i++)
{
matrix[i][i^dd]++;
matrix[i][i]++;
}
ll num;
if(n>=(1<<dd)-1) num=1<<(dd-1);
else num=n-(1<<(dd-1))+1;
if(num>cnt[dd]) pow(ans,matrix,num-cnt[dd]);
memset(matrix,0,sizeof(matrix));
for(int i=0;i<=63;i++)
matrix[i][i^dd]++;
if(cnt[dd]>0) pow(ans,matrix,cnt[dd]);
if((1<<dd)-1>=n) break;
}
printf("%lld\n",ans[0][0]);
}
//system("PAUSE");
return EXIT_SUCCESS;
}
http://poj.org/problem?id=2311
这题太蛋疼了,原因就是乍看无法再SG函数的用上(当一个人无法做出决策时那么这个人就输了,其sg=0),我开始的思路是如果当前的形状能够划分成两个必败的形状,则必胜,也不知道错哪了,折腾了好几个小时,在网上搜了代码看到用的是sg函数当(2,2) (2,3),(3,3)当做必败态,然后向上递归
贴下我的wa代码,以后再改
#include <cstdlib>
#include <iostream>
#include <cstring>
using namespace std;
int win[210][210];
void get_sg(int h,int w)
{
for(int i=2;i+2<=h;i++)
{
if(win[i][w]==-1) get_sg(i,w);
if(win[h-i][w]==-1) get_sg(h-i,w);
if(!win[i][w]&&!win[h-i][w]) { win[h][w]=1; return ; }
}
for(int i=2;i+2<=w;i++)
{
int a=h,b=i;
if(a>b) swap(a,b);
if(win[a][b]==-1) get_sg(a,b);
if(win[a][b]) continue;
a=h,b=w-i;
if(a>b) swap(a,b);
if(win[a][b]==-1) get_sg(a,b);
if(win[a][b]) continue;
win[h][w]=1; return;
}
win[h][w]=0;
}
int main(int argc, char *argv[])
{
//freopen("data.in","r",stdin);
// freopen("data.out","w",stdout);
memset(win,-1,sizeof(win));
for(int i=1;i<=200;i++)
win[1][i]=1;
win[1][1]=0;
win[2][2]=win[2][3]=win[3][3]=0;
get_sg(2,4);
for(int i=2;i<=200;i++)
for(int j=i;j<=200;j++)
if(win[i][j]==-1)
{
get_sg(i,j);
}
int a,b;
while(cin>>a>>b)
{
if(a>b) swap(a,b);
if(win[a][b]) cout<<"WIN"<<endl;
else cout<<"LOSE"<<endl;
}
// system("PAUSE");
return EXIT_SUCCESS;
}
http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1185
这题很以往组合博弈题不同,每次选择一堆石子,变成了每次最多选择3堆,然后没堆取走任意颗石子,观察二进制的每一位,一次操纵只能把每一位的改变量最多为3,如果每一位的个数都是4的倍数,那么必败;
#include <cstdlib>
#include <iostream>
#include <cstring>
using namespace std;
int main(int argc, char *argv[])
{
int n,a,cnt[33];
while(cin>>n)
{
memset(cnt,0,sizeof(cnt));
for(int i=0;i<n;i++)
{
cin>>a;
int t=0;
while(a)
{
if(a&1) cnt[t]++;
a>>=1;
t++;
}
}
int i;
for(i=0;i<31;i++)
if(cnt[i]%4!=0) break;
if(i>=31) cout<<"1"<<endl;
else cout<<"0"<<endl;
}
// system("PAUSE");
return EXIT_SUCCESS;
}
http://acm.hdu.edu.cn/showproblem.php?pid=1850
水题
#include <cstdlib>
#include <iostream>
#include <cstring>
using namespace std;
int a[110];
int main(int argc, char *argv[])
{
int n,ans;
while(cin>>n&&n)
{
ans=0;
for(int i=0;i<n;i++)
{
cin>>a[i];
ans^=a[i];
}
int sum=0;
if(ans!=0)
for(int i=0;i<n;i++)
{
if((ans^a[i])<=a[i]) sum++;
}
cout<<sum<<endl;
}
// system("PAUSE");
return EXIT_SUCCESS;
}
http://acm.hdu.edu.cn/showproblem.php?pid=2147
水题,开始我打标竟然超内存了,后来画图找规律找规矩就可以了
#include <cstdlib>
#include <iostream>
#include <cstring>
using namespace std;
int main(int argc, char *argv[])
{
int n,m;
while(cin>>n>>m&&(n||m))
{
if(get_sg1(n,m)) cout<<"Wonderful!"<<'\n';
else cout<<"What a pity!"<<'\n';
}
// system("PAUSE");
return EXIT_SUCCESS;
}
678

被折叠的 条评论
为什么被折叠?



