比赛博弈论被打烂了,场场有博弈论,场场找规律找几个小时,这两天系统地学一下博弈论,先拿这个洛谷小题单练练手。
P2197 【模板】nim 游戏
题面
s
g
sg
sg定理的模板题,
s
g
sg
sg函数为
s
g
(
i
)
=
a
i
sg(i)=a_i
sg(i)=ai,答案取异或和即可。
#include <bits/stdc++.h>
using namespace std;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
int t,n,x,ans;
int main(){
read(t);
while(t--){
ans=0;
read(n);
for(int i=1;i<=n;i++)read(x),ans^=x;
if(ans)puts("Yes");
else puts("No");
}
}
P1288 取数游戏 II
题面
考虑当前取到
i
i
i,前面的已经取过的都为
0
0
0,那么很明显只能向后取,并且每次操作必须完所有的数。
那么对于一个链, a 1 , a 2 , a 3 , . . . , a n , 0 a_1,a_2,a_3,...,a_n,0 a1,a2,a3,...,an,0,先手从左到右取,当且仅当 2 ∤ n 2\nmid n 2∤n时获胜,否则后手获胜,因此我们只需要在起点枚举向前向后取数即可。
#include <bits/stdc++.h>
using namespace std;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
int n,a[100];
int main(){
read(n);
for(int i=1;i<=n;i++)read(a[i]);
for(int i=1;i<=n;i++){
if(!a[i]){
if(i%2==0){
puts("YES");
return 0;
}
break;
}
}
for(int i=n;i;i--){
if(!a[i]){
if((n-i+1)%2==0){
puts("YES");
return 0;
}
break;
}
}
puts("NO");
}
P1290 欧几里德的游戏
题面
对于当前数对
(
n
,
m
)
(n,m)
(n,m),不妨设
m
<
n
m<n
m<n。
如果
⌊
n
m
⌋
>
1
\lfloor\frac{n}{m}\rfloor>1
⌊mn⌋>1,即可以对
(
n
,
m
)
(n,m)
(n,m)操作两次及以上,那么很显然,如果
(
m
,
n
m
o
d
m
)
(m,n\,\,mod\,\,m)
(m,nmodm)为必胜态,我们只需操作到
(
n
,
m
)
(n,m)
(n,m)只剩一次,那么就为对手必败态。
反之如果
(
m
,
n
m
o
d
m
)
(m,n\,\,mod\,\,m)
(m,nmodm)为必败态,那么只需操作到
(
m
,
n
m
o
d
m
)
(m,n\,\,mod\,\,m)
(m,nmodm),此时对手必败。
因此当
⌊
n
m
⌋
>
1
\lfloor\frac{n}{m}\rfloor>1
⌊mn⌋>1时先手必胜。
对于 ⌊ n m ⌋ = 1 \lfloor\frac{n}{m}\rfloor=1 ⌊mn⌋=1的情况,只能取 ( m , n m o d m ) (m,n\,\,mod\,\,m) (m,nmodm),因此先手必胜/必败与 m , n m o d m ) m,n\,\,mod\,\,m) m,nmodm)呈相反关系。
#include <bits/stdc++.h>
using namespace std;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
int n,m,t;
bool gcd(int a, int b){
if(!b)return 0;
if(a/b==1)return !gcd(b,a%b);
else return 1;
}
int main(){
read(t);
while(t--){
read(n),read(m);
if(m>n)swap(n,m);
if(gcd(n,m))puts("Stan wins");
else puts("Ollie wins");
}
}
P1247 取火柴游戏
题面
这题比较有意思,就是
N
i
m
Nim
Nim游戏,但是要求输出步数。
考虑到必胜态为
a
i
a_i
ai的异或和不为零,因此当异或和为
0
0
0时即为必败态,直接输出
l
o
s
e
lose
lose。
对于必胜态,设总异或和为
s
u
m
sum
sum,则对于
a
i
a_i
ai,除
a
i
a_i
ai的异或和为
s
u
m
⊕
a
i
sum\oplus a_i
sum⊕ai,我们想让下一步为必败态,即序列区间异或和为
0
0
0,故可以将
a
i
a_i
ai修改为
s
u
m
⊕
a
i
sum\oplus a_i
sum⊕ai,因此我们只需找到第一个
a
i
≥
s
u
m
⊕
a
i
a_i\ge sum\oplus a_i
ai≥sum⊕ai即可。
#include <bits/stdc++.h>
#define N 500050
using namespace std;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
int n,x,sum,a[N];
int main(){
read(n);
for(int i=1;i<=n;i++)read(a[i]),sum^=a[i];
if(sum==0){
puts("lose");
return 0;
}
for(int i=1;i<=n;i++){
if((sum^a[i])<=a[i]){
printf("%d %d\n",a[i]-(sum^a[i]),i);
a[i]=(sum^a[i]);
for(int j=1;j<=n;j++){
printf("%d",a[j]);
if(j==n)puts("");
else putchar(' ');
}
break;
}
}
}
P2252 [SHOI2002] 取石子游戏|【模板】威佐夫博弈
题面
威佐夫博弈的结论是,若较大数与较小数的差值与黄金分割比的向下取整为较小数,则该状态为先手必败,证明在第一个题解中有,这里不多赘述。
#include <bits/stdc++.h>
#define N 500050
using namespace std;
inline void read(int &x){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}
x=s*w;
}
int n,m;
double g=(sqrt(5.0)+1.0)/2.0;
int main(){
read(n),read(m);
if(n<m)swap(n,m);
int a=n-m;
if(m==int(a*g))puts("0");
else puts("1");
}