ABC218G Game on Tree 2(对顶堆)

文章介绍了如何解决一个在树结构上进行的点权中位数游戏。游戏双方A和B轮流移动棋子,目标是最大化或最小化经过的点权中位数。使用对顶堆来动态计算中位数,并在DFS遍历过程中更新每个节点的最佳得分。最终的答案是A从根节点出发能获得的最大得分。
摘要由CSDN通过智能技术生成

ABC218G Game on Tree 2

洛谷ABC218G Game on Tree 2

题目大意

给定一棵树,以及树各节点的点权(点权为偶数)。起初有一个棋子在树的根结点(结点 1 1 1)处。

A A A B B B两人轮流操作, A A A先操作,每次操作将棋子移动到其所在节点的某个儿子节点。到某个节点的得分定义为棋子经过所有结点点权的中位数。

当棋子到达某个叶节点时,游戏结束。

A A A希望最后得分尽可能大, B B B希望最后得分尽可能小。 A , B A,B A,B都采用最优策略,求最终得分。


题解

前置知识:对顶堆

我们可以先求出每个叶子节点到根节点的各个点的权值的中位数,这就要用到对顶堆了。将这棵树 d f s dfs dfs一遍,走向儿子节点就是往对顶堆中添加元素,走回父亲节点就是往对顶堆中删除元素。删除元素操作不好直接实现,可以对要删除的元素用 m a p map map来打个标记,遇到时再删除。

f u f_u fu表示走到节点 u u u时继续往下走,最终得到的中位数的值。那么,如果当前是 A A A移动,则 f u f_u fu u u u的所有儿子的最大的 f f f值;如果当前是 B B B移动,则 f u f_u fu u u u的所有儿子的最小的 f f f值。

最后, f 1 f_1 f1就是答案。

时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

code

#include<bits/stdc++.h>
using namespace std;
int n,x,y,tot=0,now,t1,t2,a[100005],f[100005],d[200005],l[200005],r[200005];
map<int,int>z1,z2;
priority_queue<int>v1,v2;
void add(int xx,int yy){
	l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;
}
void gttop1(){
	while(!v1.empty()&&z1[v1.top()]){
		--z1[v1.top()];v1.pop();
	}
}
void gttop2(){
	while(!v2.empty()&&z2[v2.top()]){
		--z2[v2.top()];v2.pop();
	}
}
void maintain(){
	gttop1();gttop2();
	if(t1>=t2+2){
		v2.push(-now);
		now=v1.top();
		v1.pop();
		++t2;--t1;
	}
	else if(t2>=t1+2){
		v1.push(now);
		now=-v2.top();
		v2.pop();
		++t1;--t2;
	}
}
void pt(int k){
	if(k<now){
		++t1;v1.push(k);
	}
	else{
		++t2;v2.push(-k);
	}
	maintain();
}
int gt(){
	gttop1();gttop2();
	if(t1==t2) return now;
	else if(t1>t2) return (now+v1.top())/2;
	else return (now-v2.top())/2;
}
void del(int k){
	if(k<now){
		--t1;++z1[k];
	}
	else if(k>now){
		--t2;++z2[-k];
	}
	else{
		gttop1();gttop2();
		if(t1>t2){
			now=v1.top();
			--t1;v1.pop();
		}
		else{
			now=-v2.top();
			--t2;v2.pop();
		}
	}
	maintain();
}
void dfs(int u,int fa,int dep){
	if(u==1) now=a[u];
	else pt(a[u]);
	if(dep&1) f[u]=0;
	else f[u]=1e9+1;
	for(int i=r[u];i;i=l[i]){
		if(d[i]==fa) continue;
		dfs(d[i],u,dep+1);
		if(dep&1) f[u]=max(f[u],f[d[i]]);
		else f[u]=min(f[u],f[d[i]]);
	}
	if(f[u]==0||f[u]==1e9+1) f[u]=gt();
	if(u>1) del(a[u]);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	dfs(1,0,1);
	printf("%d",f[1]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值