2023NOIP A层联测18 博弈树

题解

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 1n,q105


题解

如果先手的位置在直径的端点上时,先手可以将棋子移到另一个端点上,而后手不能再移动,所以此时先手必胜。

我们考虑删除所有直径端点,得到一棵新的树,如果起点在这棵树的直径上,那么先手可以将棋子移到新树的另一个直径端点,后手为了能移动更大的距离,只能将棋子移到原树的直径上,那么后手的移动距离小于原树的直径的长度,先手可以将棋子移到原树上的另一个直径端点,所以还是先手必胜。

于是,我们可以将树的直径端点不断删下去,如果最终剩下一个点,则这个点是后手必胜的(因为先手无论如何都会把这个点移动到每一层的直径端点);否则,所有点都是先手必胜。

我们发现,原树直径的中点一定是删一层后树的直径中点,于是我们可以判断原树直径的长度是否为奇数(因为是奇数的话才会有中点),如果是的话就从直径的一端暴力跳到直径的终点即可。

时间复杂度为 O ( n ) O(n) O(n)

code

#include
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;
}

你可能感兴趣的:(题解,题解,c++)