2023NOIP A层联测18 博弈树

文章讨论了在一个有n个节点的树游戏中,先手和后手如何利用树的直径特性进行策略分析。先手在直径端点有优势,通过不断删除直径端点,最后只剩一个点时判断输赢。时间复杂度为O(n)。

题解

AAA和小BBB在玩游戏,游戏规则如下:

  • 给定一棵有nnn个节点的树,小AAA和小BBB会选择一个节点作为起点放上棋子
  • 游戏由小AAA先手,轮到一方之后,玩家可以将棋子移动到树上任意一点,每次玩家移动的距离必须比对方上一次移动的距离大,开始时上一次的距离默认为000
  • 当一方不能再移动时该玩家判负

AAA和小BBB均采用最优策略。有qqq次询问,每次询问会给出一个点xxx,你需要回答以这个点为起点的情况下谁会赢。如果小AAA会赢,输出AliceAliceAlice;否则,输出BobBobBob

1≤n,q≤1051\leq n,q\leq 10^51n,q105


题解

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

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

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

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

时间复杂度为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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值