题解
小 A A A和小 B B B在玩游戏,游戏规则如下:
- 给定一棵有 n n n个节点的树,小 A A A和小 B B B会选择一个节点作为起点放上棋子
- 游戏由小 A A A先手,轮到一方之后,玩家可以将棋子移动到树上任意一点,每次玩家移动的距离必须比对方上一次移动的距离大,开始时上一次的距离默认为 0 0 0
- 当一方不能再移动时该玩家判负
小 A A A和小 B B B均采用最优策略。有 q q q次询问,每次询问会给出一个点 x x x,你需要回答以这个点为起点的情况下谁会赢。如果小 A A A会赢,输出 A l i c e Alice Alice;否则,输出 B o b Bob Bob。
1 ≤ n , q ≤ 1 0 5 1\leq n,q\leq 10^5 1≤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;
}