博弈论题集&构造题集
2021/04/19 ABS https://vjudge.net/problem/AtCoder-arc085_b/origin
题意:
两个人X和Y手中两张卡数值为Z和W。现在有N张卡,两人轮流取(取至少一张 可以全取完)。然后只留下最后取的一张卡。X想让他和Y手中卡数值差距绝对值最大,Y想让最小。
求两者都采取最佳策略下最后的数值差距
题解:
X取最后一张或者倒数第二张,取最优差距。
#include <stdio.h> #include <cstring> #include <cmath> #include <iostream> using namespace std; typedef long long ll; const ll mx=2e3+10; ll N, Z, W; ll a[mx]; void solve(){ scanf("%lld %lld %lld", &N, &Z, &W); for(ll i=1;i<=N;i++)scanf("%lld", &a[i]); if(N==0){ printf("%lld\n", abs(Z-W)); return; } if(N==1){ printf("%lld\n", abs(a[1]-W)); return; } ll ans1=abs(a[N-1]-a[N]); ll ans2=abs(a[N]-W); printf("%lld\n", max(ans1, ans2)); } int main(){ solve(); return 0; }
2021/05/05
题目:
https://vjudge.net/contest/387837#problem/A
https://vjudge.net/problem/POJ-2234/origin
题意:
有n堆石子,每个人随便从某一堆取任意个(至少为1),谁无法取了谁就输。问谁输
题解:
https://www.cnblogs.com/zlrrrr/p/10596717.html
好愁 已经把博弈论内容全忘了
sg函数
对于任意状态 x , 定义 SG(x) = mex(F),其中F 是 x 后继状态的SG函数值的集合(就是上述mex中的数值)。最后返回值(也就是SG(X))为0为必败点,不为零必胜点。
#include <iostream> #include <cstring> #include <cstdio> typedef long long ll; const int mx=1e3+10; using namespace std; ll sg[mx]; bool has[mx]; /* 对于任意状态 x , 定义 SG(x) = mex(F), 其中F 是 x 后继状态的SG函数值的集合(就是上述mex中的数值)。 最后返回值(也就是SG(X))为0为必败点,不为零必胜点。 */ void getSg(ll len){ memset(sg, 0, sizeof(sg)); for(ll i=1;i<len;i++){//枚举当前石子的个数 memset(has, false, sizeof(has)); for(ll j=1;j<=i;j++){ has[i-j]=true; } for(ll j=0;;j++){ if(has[j]==false){ sg[i]=j; break; } } } } int main(){ // getSg(1e3); ll num, tmp; while(scanf("%lld", &num)!=EOF){ ll v=0; for(ll i=1;i<=num;i++){ scanf("%lld", &tmp); v=v^tmp; } printf("%s\n", v==0?"No":"Yes");//判断条件不能为v>0 } return 0; }
2021/05/14
题目:
https://vjudge.net/contest/387837#problem/B
https://vjudge.net/problem/POJ-1704/origin
题意:
n个棋子放在一行上,第i个棋子的位子为pi(1<=n<=1000, 1<=pi<=10000)。b和g两人轮流右移一个棋子任意步数(但是每次移动不能超过右边的棋子
题解:
可以把题意转化为nim。令任意相邻的两个棋子之间的空的个数为一堆石子的个数。那么移动一个棋子x步,就是往一堆石子里面拿走x个,往另一个石子堆里面添加x个。
#include <iostream> #include <cstring> #include <algorithm> #include <cstdio> typedef long long ll; const int mx=1e3+10; using namespace std; ll tmp[mx]; int main(){ ll t; scanf("%lld", &t); for(ll time=1;time<=t;time++){ ll num, v=0; scanf("%lld", &num); for(ll i=1;i<=num;i++){ scanf("%lld", &tmp[i]); } sort(tmp+1, tmp+num+1); ll i; if(num&1){ v=tmp[1]-1; i=2; } else{ v=0; i=1; } for(;i+1<=num;i++){ v=v^(tmp[i+1]-tmp[i]-1); } printf("%s\n", v==0?"Bob will win":"Georgia will win"); } return 0; }
2021/05/19
题目:C - GG and MM
https://vjudge.net/contest/387837#problem/C
http://acm.hdu.edu.cn/showproblem.php?pid=3595
题意:everysg
n组游戏,每组有两堆石子,个数为p和q。每个人每轮,令少的一堆个数为x,大的个数为y,那么可以从y里面拿走kx个(kx<=y)。每轮都要把每一组能继续执行的游戏执行以上操作。
最后什么操作都无法执行的人输。
题解:everysg
https://www.cnblogs.com/zwfymqz/p/8470381.html
-对于SG值为0的点,我们需要知道最少需要多少步才能走到结束【必败态】
-对于SG值不为0的点,我们需要知道最多需要多少步结束【必胜态】
用step变量来记录这个步数
对于Every-SG游戏先手必胜当且仅当单一游戏中最大的step为奇数。
#include<iostream> #include<algorithm> #include<vector> #include<cstring> using namespace std; const int mx=1e3+10; const int inf=1<<30; typedef long long ll; ll N, p, q; ll sg[mx][mx], step[mx][mx]; ll getsg(ll x, ll y){ if(sg[x][y]!=-1)return sg[x][y]; if(x>y)swap(x, y); //x<y ll k=y/x, r=y%x; if(k==1){ ll i=y-x;//只有一种做法 sg[x][y]=sg[y][x]=getsg(x, i)^1; step[x][y]=step[y][x]=step[x][i]+1; } else{ //当前有两种走法 所以当前必然赢 sg[x][y]=sg[y][x]=1; ll v=getsg(x, r); //如果v==0 说明后一个人必然输 //如果v==1 说明要留一步给对方 然后再加上1步 // step[x][y]=step[y][x]=getsg(x, r)+step[x][r]+1; if(v==0){//后手必然输这种走法 getsg(x, r); step[x][y]=step[y][x]=step[x][r]+1; } else{ getsg(x, r+x); step[x][y]=step[y][x]=step[x][r+x]+1; } } return sg[x][y]; } int main(){ memset(sg, -1, sizeof(sg)); for(ll i=0;i<mx;i++){ sg[i][0]=sg[0][i]=0;//先手必输 step[i][0]=step[0][i]=0; } while(scanf("%lld", &N)!=EOF){ ll maxstep=0; for(ll i=1;i<=N;i++){ scanf("%lld %lld", &p, &q); getsg(p, q); maxstep=max(maxstep, step[p][q]); } printf("%s\n", maxstep&1?"MM":"GG"); } return 0; }
wa了很久。。
刚开始没有理解参考题解里面的k。后来想明白了。如果k>2,先手想要不给对方控制的机会,那么要么直接取y%x,这样对方就只能调换取石子的堆了(因为两个堆里哪个堆的石子多 变了)。也可以只留下y%x+x,这样对方就只能在当前较小的堆里取且只能取一个x。
那么对于k==1的情况,就直接取,sg函数异或1切换。
对于k==0的情况 题解有个公式一直妹看懂后来理解了。。。
step[x][y]=step[y][x]=getsg(x, r)+step[x][r]+1;
如果getsg(x,r)为0,说明取到头的时候后手必输,sg=0,步数直接+1就行了,也就是step[x][r]+1,此时sg=0不影响结果;
反之不为0,为1。说明我们不能取到头,要给对方留一步,让对方去走不好的路。那么从x,y到x,r中间还要多加一步x,r+x。所以应该是step[x][r]+1+1, 而此时sg为1,所以可以直接用sg来代表后面新增的1。
2021/05/19
题目:E - 取石子游戏
https://vjudge.net/contest/387837#problem/E
http://acm.hdu.edu.cn/showproblem.php?pid=2516
题意:
一堆石子n个。第一次取任意个(不能全取完)。之后每次取不能超过之前的两倍(可以全取完)。取完的人胜。
题解:斐波那契博弈。。。(真的全忘了。。
https://blog.csdn.net/acm_cxlove/article/details/7835016
“Zeckendorf定理”(齐肯多夫定理):任何正整数可以表示为若干个不连续的Fibonacci数之和。
呜呜呜太难了太难了
#include<iostream> #include<algorithm> #include<vector> #include<map> #include<cstring> using namespace std; const int mx=50+10; const int inf=1<<30; typedef long long ll; ll f[mx]; map<ll, ll>mp; void init(){ f[1]=1, f[2]=2; // ll v=(ll)1<<32; for(ll i=3;i<mx;i++){ f[i]=f[i-1]+f[i-2]; mp[f[i]]=1; } } int main(){ init(); ll v; while(1){ scanf("%lld", &v); if(v==0)break; printf("%s win\n", mp[v] == 1 ? "Second" : "First"); } return 0; }
2021/05/19
题目:F - A Multiplication Game
https://vjudge.net/contest/387837#problem/F
http://acm.hdu.edu.cn/showproblem.php?pid=1517
题意:
s和o玩游戏,s从1开始,接下来每人轮着从2到9选一个数字乘上去,谁先大于等于n谁就赢。
题解:
从后往前想。谁大于n谁就赢。那么也就是说 谁先大于n2=ceil(n/9.0)并且够不到n谁就输,因为后手必然可以到达n。
继续推,谁先到达n3=ceil(n/2.0)谁就赢,因为它可以确保后手到达n2且无法到达n。
。。。
以此类推。
只需要最后判断谁是赢的先手就可以了。
#include<iostream> #include<algorithm> #include<vector> #include<map> #include<cmath> #include<cstring> using namespace std; const int mx=50+10; const int inf=1<<30; typedef long long ll; ll time(ll v, ll id){ if(v==1) return 0; v=(id==1?ceil(v/9.0):ceil(v/2.0)); return time(v, id^1)+1; } void solve() { ll v; while(scanf("%lld", &v)!=EOF){ ll t=time(v, 1); printf("%s wins.\n", t & 1 ? "Stan" : "Ollie"); } } int main(){ solve(); return 0; }
此外,我看到了我九个月前的代码。。当时是从前往后推,只要一直乘以18就行。然后最后判断x9能不能到达n。能就是stan赢。(其实就是把倒过来的思路扳回去了)