博弈论总结
Posted Harris-H
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了博弈论总结相关的知识,希望对你有一定的参考价值。
博弈论总结
博弈图中的状态
关于Nim和的证明
SG定理
用SG定理证明Nim和的结论
来自某大佬的总结
习题
P4101 [HEOI2014]人人尽说江南好
整体考虑,注意到答案取决于合并次数的奇偶性,则考虑需要合并多少次。
显然最终结果必然是: m , m , m … , n ( m o d m ) m,m,m\\dots,n\\pmod m m,m,m…,n(modm)
合并的次数即为: n − ⌈ n m ⌉ n-\\lceil\\dfrac{n}{m}\\rceil n−⌈mn⌉
P2575 高手过招
阶梯Nim+子游戏合并好题。
把一个空格两边的连续黑棋子看作一个台阶。
然后最右边加一个空格,相当于问题变成有若干层台阶,每次可以移动一个台阶的若干石子到右边的台阶,最后所有的棋子会到最右边的空格。
然后就裸了。注意台阶可以没有棋子,即多个空格不能算成一个空格。
因为行之间不会相互影响,所以相当于玩若干个子游戏。结果异或起来就可以了。
code
// Problem: P2575 高手过招
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2575
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// Date: 2021-07-09 10:07:35
// --------by Herio--------
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e3+5,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb emplace_back
#define SZ(a) (int)a.size()
#define ios ios::sync_with_stdio(false),cin.tie(0)
void Print(int *a,int n){
for(int i=1;i<n;i++)
printf("%d ",a[i]);
printf("%d\\n",a[n]);
}
int sg(){
int m;scanf("%d",&m);
int s=0,f=0;
int ans=0;
int a[22]={};
for(int i=1;i<=m;i++){
int x;scanf("%d",&x);a[x]=1;
}
for(int i=20;~i;i--){
if(a[i]) s++;
else {
if(f) ans^=s;
s=0;
f^=1;
}
}
return ans;
}
int main(){
int t;scanf("%d",&t);
while(t--){
int n;scanf("%d",&n);
int ans=0;
for(int i=1;i<=n;i++){
ans^=sg();
}
puts(ans?"YES":"NO");
}
return 0;
}
P2964 [USACO09NOV]A Coin Game S
经典博弈论dp,因为当前选取的情况取决于上一个人选取的个数,不妨设
d p [ i ] [ j ] dp[i][j] dp[i][j]当前剩下 i i i个硬币,上一个人取了 j j j个的答案。
注意到 d p [ i ] [ j ] dp[i][j] dp[i][j]包含 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1],因为上一个取更多答案不会更劣。
所以我们只需考虑转移 k = 2 j − 1 , 2 j k=2j-1,2j k=2j−1,2j这两种情况。
a i a_i ai是从右往左的前缀和。
d p [ i ] [ j ] = m a x { d p [ i ] [ j − 1 ] , s [ i ] − d p [ i − k ] [ k ] , s [ i ] − d p [ i − ( k + 1 ) ] [ k + 1 ] } dp[i][j]=max\\{dp[i][j-1],s[i]-dp[i-k][k],s[i]-dp[i-(k+1)][k+1]\\} dp[i][j]=max{dp[i][j−1],s[i]−dp[i−k][k],s[i]−dp[i−(k+1)][k+1]}
时间复杂度: O ( n 2 ) O(n^2) O(n2)
for(int i=1;i<=n;i++)
for(int j=1,k=2*j-1;j<=n;j++,k+=2){
dp[i][j]=dp[i][j-1];
if(k<=i) dp[i][j]=max(dp[i][j],a[i]-dp[i-k][k]);
if(k+1<=i) dp[i][j]=max(dp[i][j],a[i]-dp[i-k-1][k+1]);
}
P2734 [USACO3.3]游戏 A Game
经典博弈论dp,类似区间dp。
for(int i=1;i<=n;i++) a[i]+=a[i-1];
for(int k=1;k<=n;k++)
for(int l=1,r=l+k-1;r<=n;l++,r++){
f[l][r]=max(f[l][r],a[r]-a[l-1]-f[l+1][r]);
f[l][r]=max(f[l][r],a[r]-a[l-1]-f[l][r-1]);
}
P5675 [GZOI2017]取石子游戏
经典Nim套个dp。
显然限制了第一堆的选取后,游戏的胜负取决于其他堆的石子的异或和 s u m sum sum
显然 s u m ≥ a i sum\\ge a_i sum≥ai ,不然先手总可以通过拿走第一堆石子的若干个使得异或和为0,这样后手就输了。
因此可以设置状态: f [ i ] [ j ] f[i][j] f[i][j]前 i i i堆石子异或和为 j j j的方案数。
然后就正常dp。
void dp(int x){
for(int i=1;i<=n;i++)
for(int j=0;j<N;j++){
f[i][j]=(i==x)?f[i-1][j]:(f[i-1][j]+f[i-1][j^a[i]])%mod;
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
f[0][0]=1;
for(int i=1;i<=n;i++){
dp(i);
for(int j=a[i];j<N;j++)
ans=(ans+f[n][j])%mod;
}
printf("%lld\\n",ans);
return 0;
}
SG函数习题二刷
Fibonacci again and again
以fibnacci 的前几项为状态转移,暴力跑sg即可。
void solve(){
bitset<N>mex;
for(int i=1;i<N;i++){
mex.reset();
int j;
for(j=1;j<=15&&f[j]<=i;j++)
mex[sg[i-f[j]]]=1;
for(j=0;;j++)
if(!mex[j]) break;
sg[i]=j;
}
}
int main(){
f[1]=f[2]=1;for(int i=3;i<=15;i++) f[i]=f[i-1]+f[i-2];
while(~scanf("%d%d%d",&n,&m,&p)){
if(!n&&!m&&!p) break;
solve();
puts((sg[n]^sg[m]^sg[p])?"Fibo":"Nacci");
}
return 0;
}
HDU 1536 S-Nim
SG板子水题,注意先对状态转移数组排序。
// Problem: S-Nim
// Contest: Virtual Judge - LibreOJ
// URL: https://vjudge.net/problem/LibreOJ-10247
// Memory Limit: 524 MB
// Time Limit: 1000 ms
// Date: 2021-07-09 15:05:18
// --------by Herio--------
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e4+5,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb emplace_back
#define SZ(a) (int)a.size()
#define IOS ios::sync_with_stdio(false),cin.tie(0)
void Print(int *a,int n){
for(int i=1;i<n;i++)
printf("%d ",a[i]);
printf("%d\\n",a[n]);
}
int n,m,k;
int f[N],sg[以上是关于博弈论总结的主要内容,如果未能解决你的问题,请参考以下文章