题解
小AAA和小BBB在玩游戏,游戏规则如下:
- 给定一棵有nnn个节点的树,小AAA和小BBB会选择一个节点作为起点放上棋子
- 游戏由小AAA先手,轮到一方之后,玩家可以将棋子移动到树上任意一点,每次玩家移动的距离必须比对方上一次移动的距离大,开始时上一次的距离默认为000
- 当一方不能再移动时该玩家判负
小AAA和小BBB均采用最优策略。有qqq次询问,每次询问会给出一个点xxx,你需要回答以这个点为起点的情况下谁会赢。如果小AAA会赢,输出AliceAliceAlice;否则,输出BobBobBob。
1≤n,q≤1051\leq n,q\leq 10^51≤n,q≤105
题解
如果先手的位置在直径的端点上时,先手可以将棋子移到另一个端点上,而后手不能再移动,所以此时先手必胜。
我们考虑删除所有直径端点,得到一棵新的树,如果起点在这棵树的直径上,那么先手可以将棋子移到新树的另一个直径端点,后手为了能移动更大的距离,只能将棋子移到原树的直径上,那么后手的移动距离小于原树的直径的长度,先手可以将棋子移到原树上的另一个直径端点,所以还是先手必胜。
于是,我们可以将树的直径端点不断删下去,如果最终剩下一个点,则这个点是后手必胜的(因为先手无论如何都会把这个点移动到每一层的直径端点);否则,所有点都是先手必胜。
我们发现,原树直径的中点一定是删一层后树的直径中点,于是我们可以判断原树直径的长度是否为奇数(因为是奇数的话才会有中点),如果是的话就从直径的一端暴力跳到直径的终点即可。
时间复杂度为O(n)O(n)O(n)。
code
#include<bits/stdc++.h>
using namespace std;
const int N=100000;
int n,q,tot=0,d[2*N+5],l[2*N+5],r[N+5],dep[N+5],fa[N+5];
void add(int xx,int yy){
l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;
}
void dfs(int u,int f){
fa[u]=f;
dep[u]=dep[f]+1;
for(int i=r[u];i;i=l[i]){
if(d[i]==f) continue;
dfs(d[i],u);
}
}
int main()
{
// freopen("tree.in","r",stdin);
// freopen("tree.out","w",stdout);
scanf("%d%d",&n,&q);
for(int i=1,x,y;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
dfs(1,0);
int v1=0,v2=0,vb=0;
for(int i=1;i<=n;i++){
if(dep[i]>dep[v1]) v1=i;
}
dfs(v1,0);
for(int i=1;i<=n;i++){
if(dep[i]>dep[v2]) v2=i;
}
if(dep[v2]&1){
vb=v2;
for(int i=1;i<=dep[v2]/2;i++) vb=fa[vb];
}
for(int i=1,x;i<=q;i++){
scanf("%d",&x);
if(x==vb) printf("Bob\n");
else printf("Alice\n");
}
return 0;
}
文章讨论了在一个有n个节点的树游戏中,先手和后手如何利用树的直径特性进行策略分析。先手在直径端点有优势,通过不断删除直径端点,最后只剩一个点时判断输赢。时间复杂度为O(n)。
1175

被折叠的 条评论
为什么被折叠?



