CF48E Ivan the Fool VS Gorynych the Dragon
Gorynych 有 \(H\) 个头,\(T\)条尾巴,每次 Ivan 可以砍掉 \(0\) 到 \(N\) 个头或者 \(0\) 到 \(M\) 条尾巴,但是每当 Ivan 砍掉 Gorynych 的头或者尾巴时,总有一些新头和新尾巴长出来。
如果 Gorynych 的头和尾巴数量之和超过给定的数字 \(R\) ,则 Gorynych 会战胜 Ivan;如果 Ivan 砍掉了 Gorynych 所有的头和尾巴,则 Ivan 取胜;
Ivan 会按照最优策略进攻,即能赢要求尽量赢,而且回合数最小化;赢不了尽量平,也就是无限进行下去;如果平不了则尽量撑更长时间,也就是回合数最大化。
直接暴力建图。然后按题意模拟。如果能赢用 bfs 找出最短路。如果不能赢就找环。有环就会无限下去平局,否则就找输了的最长步数。
代码不想写(?
CF36D New Game with a Chess Piece
有一个 \(n \times m\) 的棋盘,每一步可以从 \((x,y)\) 移动到 \((x+1,y),(x,y+1),(x+k,y+k)\) ,不能移到棋盘外。
从 \((1,1)\) 开始,不能移动者负,Alice和Bob都按照最优策略移动,问先手是否胜利。
先找规律。不考虑 \(k\) 先填一下胜负。
... | ... | ... | ... | 0 | 1 |
---|---|---|---|---|---|
... | ... | ... | ... | 1 | 0 |
0 | 1 | 0 | 1 | 0 | 1 |
1 | 0 | 1 | 0 | 1 | 0 |
发现最后都是 \(0\) 和 \(1\) 交替。
考虑有 \(k(k>1)\) 的情况。继续打表。发现此时对于一个 \(n=t(k+1),m>n\) 都一定是先手必胜。
发现对于一个 \((k+1)\times(k+1)\) ,\((1,1)\) 和 \((k+1,k+1)\) 的胜率是相反的。所以先手必胜。
而对于一个 \(2(k+1) \times 2(k+1)\) ,先手也是必胜的。
所以可以发现,设 \(x=\min(n,m)\) ,若 \(x \equiv 0 \pmod{(k+1)}\) 则一定是先手必胜。
否则,设 \(p=\left\lfloor \dfrac{x}{k+1} \right\rfloor\) 。打表发现此时对于取膜后的矩阵范围都是 \(0\) 和 \(1\) 交替。如果 \(p\) 和 \((n+m)\) 的奇偶性相同,则先手败,反之先手胜。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int T,n,m,k;
int main()
{ freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
scanf("%d%d",&T,&k);
while(T--)
{ scanf("%d%d",&n,&m);
if(n<m)swap(n,m);
if(k==1)
{ if((n&1)&&(m&1))printf("-\n");
else printf("+\n");
continue;
}
if(m%(k+1)==0)printf("+\n");
else{
int p=m/(k+1);
if((p&1)==((n+m)&1))printf("-\n");
else printf("+\n");
}
}
return 0;
}
CF39E What Has Dirichlet Got to Do with That?
Masha和Stas在玩游戏。初始有两个数 \(a,b\),令 \(f(a,b)\) 表示把 \(b\) 个不同的物品放入 \(a\) 个不同的箱子的方案数,可以允许有箱子啥都不放。
Masha和Stas每次可以增加一个箱子或一个物品,但是要求 \(f(a,b)\) 小于给定的 \(k\) ,无法操作者负。Masha先手,两边都按照最优策略走,问谁能赢得胜利 。
其中 \(a \le 10^4,b \le 30,k \le 10^9\) 。
首先,\(f(a,b)=a^b\) 。
因为 \(k \le 10^9\) ,所以 \(f(a,b)\) 的状态数太多,没办法暴力。
但是若先去掉 \(a=1,b=1\) 的情况,情况就少了很多。\(a\) 的上限变成 \(\sqrt{k}\) ,而 \(b\) 变成 \(\log{k}\) 。
所以直接对于所有 \(a>1,b>1\) 暴力预处理,记为 \(g(a,b)\) 。再考虑 \(a=1,b=1\) 。
首先是 \(a=1,b>1\) 。如果此时 \(f(a+1,b)>k\) 则此时只会不停的增加 \(b\) ,也就是说游戏会无限进行下去。
但在平局前可能存在一段是 \(f(a+1,b)<k\) 。也就是说,如果此时的 \(g(2,b)\) 是后手必胜,则此时的先手一定会选择 \(a+1\) 。
当是 \(a>1,b=1\) 时。同样的,如果此时 \(f(a,b+1)=a^2>k\) 即 \(a>\sqrt{k}\) 时只会不停增加 \(a\) 。此时就变成了当前先手和 \(n-a-1\) 的奇偶性讨论。
在这过程前,如果出现一个状态 \(g(a,2)\) 是后手必胜,则此时的先手选择 \(b+1\) 而获胜。
当时 \(a=1,b=1\) 时。分别讨论 \(f(1,2)\) 和 \(f(2,1)\) 的情况即可。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=35000+5,M=31;
int n,a,b,dp[N][M];
bool check(int x,int y)
{ ll res=1ll;
for(int i=1;i<=y;i++)
{ res=res*x;
if(res<=0||res>=n)return 1;
}
return 0;
}
int dfs(int x,int y)
{ if(dp[x][y]!=-1)return dp[x][y];
if(check(x,y))return dp[x][y]=1;
int res=0;
res|=!dfs(x+1,y),res|=!dfs(x,y+1);
return dp[x][y]=res;
}
int checka(int y)
{ if(check(1,y))return 0;
int sg=0;
while(!check(2,y))
{ if(dp[2][y]==0)return (sg==0)?(1):(-1);
sg^=1;y++;
}
return 0;
}
int checkb(int x)
{ int lim=sqrt(n-0.000001);
int sg=0;
while(x<=lim)
{ if(dp[x][2]==0)return (sg==0);
x++;sg^=1;
}
sg=(sg+n-1-x)%2;
return sg;
}
int main()
{ scanf("%d%d%d",&a,&b,&n);
if(check(a,b)){printf("Masha\n");return 0;}
memset(dp,-1,sizeof(dp));
for(int i=2;i<=N-5;i++)
for(int j=2;j<M;j++)
dp[i][j]=dfs(i,j);
if(a!=1&&b!=1)
{ if(dp[a][b])printf("Masha\n");
else printf("Stas\n");
return 0;
}
if(a==1&&b==1)
{ int x=checka(b+1),y=checkb(a+1);
if(x<0||y==0)printf("Masha\n");
else if(x==0)printf("Missing\n");
else printf("Stas\n");
return 0;
}
if(b==1)
{ if(checkb(a))printf("Masha\n");
else printf("Stas\n");
}
else if(a==1)
{ int x=checka(b);
if(x==0)printf("Missing\n");
else if(x==1)printf("Masha\n");
else printf("Stas\n");
}
return 0;
}
CF768E Game of Stones
Sam 和 Jon 在玩 Nim 游戏,但是他们厌倦了 Nim 游戏,于是换了一个规则。
每一次一个人在一堆石子中选择某个数目的石子拿走后,另一个人就不能在同一堆石子中选择同样数目的石子拿走。其他规则不变,两边都按最优策略走,问谁能赢。
其中 \(n \le 10^6,x \le 60\) 。
首先先打个表,可以发现 \(1,1,2,2,2,3,3,3,3,4,4,4,4,4,...\) 。也就是说有 \(i+1\) 个 \(i\) 。
也就是 \(sg(x)=\max\{ a|1+2+3+..+a \le x \}\)
来尝试理解一下为什么。
因为每堆石子最多选 \(a\) 次,也就是相当于一个大小为 \(a\) 的普通 Nim 游戏。
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
int n,a,sg;
int main()
{ scanf("%d",&n);
for(int i=1,j;i<=n;i++)
{ scanf("%d",&a);
for(j=1;;j++)
{ a-=j;
if(a<=j)break;
}
sg^=j;
}
if(sg)printf("NO\n");
else printf("YES\n");
return 0;
}
CF794E Choosing Carrot
A 和 B 得到了 \(n\) 个胡萝卜,需要最终选出一个。每个胡萝卜有特征值 \(a_i\),A 认为 \(a_i\) 大的胡萝卜好,而 B 认为 \(a_i\) 小的胡萝卜好。
他们每次都会在当前序列中最左端或最右端拿掉一个胡萝卜, 最后剩下的一个就是选出的胡萝卜。
A 先手,两个人都足够聪明( A 最大化 \(a_i\) 而 B 最小化 \(a_i\) ),A 为了使自己胜算更大,会在游戏开始之前趁 B 不注意先按照之前的规则移动 \(k\) 步。
对于所有 \(0 \le k \le n-1\),求出最后剩下的胡萝卜的 \(a_i\) 值大小.
其中 \(n \le 3 \times 10^5,a_i \le 10^9\) 。
对于 \(n\) 是偶数的情况,最终答案一定在中间两者之一。因为目的相反,一个人做出决策一定是对另一个人不利的。
所以第二个人会做出相反的决策。最后答案就在中间了。
如果是奇数,则答案是 \(\max(\min(a_{i-1},a_i),\min(a_i,a_{i+1}))\) 。
所以对于每个位置的奇数偶数情况的答案即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=3e5+5;
int n,a[N],h[N],g[N],ans[N];
int main()
{ scanf("%d",&n);
for(int i=1;i<=n;i++)
{ scanf("%d",&a[i]);
ans[1]=max(ans[1],a[i]);
}
for(int i=1;i<n;i++)
h[min(i,n-i)]=max(h[min(i,n-i)],max(a[i],a[i+1]));
for(int i=2;i<n;i++)
g[min(i-1,n-i)]=max(g[min(i-1,n-i)],max(min(a[i],a[i-1]),min(a[i],a[i+1])));
for(int i=n/2;i>=1;i--)
{ ans[i<<1]=max(ans[(i+1)<<1],h[i]);
ans[i<<1|1]=max(ans[(i+1)<<1|1],g[i]);
}
for(int i=n;i>=1;i--)
printf("%d ",ans[i]);
printf("\n");
return 0;
}
CF98E Help Shrek and Donkey
有一副互不相同的牌共 \(n+m+1\) 张。有两个人,第一个人 \(n\) 张,第二个人 \(m\) 张。另外有一张牌放在桌子上。
两个人玩游戏轮流操作(其中第一个人为先手)。有如下 \(2\) 中操作类型:
- 猜测:猜桌上的那张牌是什么。如果猜对了则获胜,猜错了则失败。操作完之后游戏结束。
- 指定:报一张牌的名字,如果对方手上有这张牌,则将该牌丢弃,游戏继续;如果对方手上没有这张牌,对方则会表示他不用由此牌。
现在假设两个人都知道这 \(n+m+1\) 张牌分别是什么,但是不知道桌上和对方手里的牌具体是什么。
若双方都采取最优策略进行游戏,问先手和后手获胜概率。
其中 \(n,m \le 10^3\) 。
首先先手可以选择如实问对方没有的(1),问对方自己有的。或者选择欺骗(2)。
后手可以选择相信先手(1),或对方认为先手在骗他(2)
分类。记 \(f(n,m)\) 表示先手有 \(n\) 张后手有 \(m\) 张的概率。
对于 \((2,1)\) 的情况,先手直接获胜,概率是 \(1\) 。
对于 \((2,2)\) 的情况,后手判断出这张牌是在先手手里的,所以概率是 \(1-f(m,n-1)\) 。
对于 \((1,1)\) 的情况,如果此时先手没猜到了桌上的牌才继续游戏。先手获胜的概率为 \(\dfrac{m}{m+1} \times (1-f(m-1,n))\) 。
对于 \((1,2)\) 的情况,先手获胜的概率是 \(\dfrac{1}{m+1}+\dfrac{m}{m+1}\times (1-f(m-1,n))\) 。
设先手有 \(p\) 的概率走 \((1)\) ,\((1-p)\) 的概率选择 \((2)\) 。此时就可以算出后手选 \((1)\) 或者 \((2)\) 的胜率。
我们可以通过调整 \(p\) 来使自己的胜率最大。
纳什均衡:每个玩家选择一个策略,当一个玩家不改变策略时,没有玩家能从改变策略中获益。
可以得出 \(p \cdot w(1,1)+(1-p) \cdot w(2,1)=p \cdot w(1,2)+(1-p) \cdot w(2,2)\) 。
解得 \(p=\dfrac{w(2,2)-w(2,1)}{w(1,1)-w(2,1)-w(1,2)+w(2,2)}\) 。于是转移变为 \(f(n,m)=w(1,1) \cdot p+(1-p)\cdot w(2,1)\) 。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=1000+5;
int m,n;double f[N][N];
double dp(int x,int y)
{ if(f[x][y])return f[x][y];
if(!y)return f[x][y]=1.0;
if(!x)return f[x][y]=1.0/(y+1);
double w11=1.0*y/(y+1)*(1.0-dp(y-1,x)),w21=1.0,w12=w11+1.0/(y+1),w22=1.0-dp(y,x-1);
double res=(w21-w22)/(w12+w21-w11-w22);
return f[x][y]=res*w11+(1.0-res)*w21;
}
int main()
{ scanf("%d%d",&m,&n);
double res=dp(m,n);
printf("%.10lf %.10lf\n",res,1.000-res);
return 0;
}