比赛博弈论被打烂了,场场有博弈论,场场找规律找几个小时,这两天系统地学一下博弈论,先拿这个洛谷小题单练练手。
题面
s g sg sg定理的模板题, s g sg sg函数为 s g ( i ) = a i sg(i)=a_i sg(i)=ai,答案取异或和即可。
#include
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");
}
}
题面
考虑当前取到 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
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");
}
题面
对于当前数对 ( n , m ) (n,m) (n,m),不妨设 m < n m
如果 ⌊ 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
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");
}
}
题面
这题比较有意思,就是 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
#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;
}
}
}
题面
威佐夫博弈的结论是,若较大数与较小数的差值与黄金分割比的向下取整为较小数,则该状态为先手必败,证明在第一个题解中有,这里不多赘述。
#include
#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");
}