题目大意
给定一棵树,以及树各节点的点权(点权为偶数)。起初有一个棋子在树的根结点(结点 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;
}