[ABC 218 G] Game on Tree 2
Game on Tree 2
题目大意
给定一棵树,树上每个节点都有一个点权。
\(A\) 和 \(B\) 要在这棵树上玩一个游戏,具体步骤如下:
-
从节点 \(1\) 开始,每个人操作一次,每次操作到达当前节点的儿子节点。
-
当前的值指目前所到达所有节点点权集合的中位数, \(A\) 希望这个值更大, \(B\) 希望这个值更小。
-
\(A\) 先手,且两人都采用最优策略,到达叶子节点后游戏结束,中途不能结束。
求最后的值。
分析
粗略想法
首先,我们很容易发现,每一个叶子节点对应一条完整路径,最后的答案必然产生在这些路径中。
对于这些路径,我们可以将它预先处理出来,将它存入叶子结点中。具体操作为 \(DFS\) 暴力记录,到达叶子结点再 \(sort\) 排序,之后按照定义求中位数即可,时间复杂度看起来很可过。
我们有了这个值之后应该怎么操作?
思考如何表达最优策略这个限制。
之后发现对于每一个节点应该由谁来操作我们是可以确定的,那我们应该选择的策略也可以确定。即树形 \(DP\) ,从根节点向上更新,按照当前节点操作人来选择操作方法,则我们得到的点值即可以等价为如果到达了这个节点最后必定会得到的答案。
最后根节点的值就是我们的答案。
优化
将这个想法实现之后,结果是超时了六个点。
仔细算了一下时间复杂度,会发现预处理很容易被卡掉。
考虑一个长为 \(n/2\) 的链,链的末端连接 \(n/2\) 个叶子,我们这样做的复杂度应该是达到了 \(O(\frac{n^2}{4}log\frac{n}{2})\) ,而 \(n\) 的极限在 \(1e5\) ,显然是会爆掉的。
于是我们需要考虑一个对顶堆优化。
关于对顶堆,其实相当于我们将原来单调递增的一个队列从中间砍成了两个队列,满足第一个队列的长度大于第二个队列的长度且长度最多相差为 \(1\) ,我们可以用 \(multiset\) 维护这两个队列,如果队列长度相等,则队列一的末端加上队列二的开端除 \(2\) ,就是我们的中位数,否则,队列一的末端即使中位数。
这样做的复杂度,应该是 \(O(nlogn)\) 的,这就显然不会超时了。
所以在我们之前操作的基础上使用对顶堆,就能过掉这道题。
CODE
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,INF=1e15;
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') w*=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
int n;
int A[N];
int top,f[N],ans[N];
int tot,v[2*N],nex[2*N],first[N];
multiset<int> s1,s2;
inline void Add(int x,int y)
{
nex[++tot]=first[x];
first[x]=tot,v[tot]=y;
}
inline void DFS(int x,int fa)
{
bool flag=false;
if(x==1) s1.insert(A[x]);
else{
auto it=s1.end();
it--;
if(A[x]<=*it){
if(s1.size()==s2.size()) s1.insert(A[x]);
else{
s1.insert(A[x]);
auto th=s1.end();
th--;
s2.insert(*th),s1.erase(th);
}
}
else{
if(s1.size()==s2.size()){
s2.insert(A[x]);
auto th=s2.begin();
//cout<<*th<<"\n";
s1.insert(*th),s2.erase(th);
}
else s2.insert(A[x]);
}
}
for(register int i=first[x];i;i=nex[i]){
if(v[i]==fa) continue;
flag=true; //该节点不为叶子结点
DFS(v[i],x);
}
if(!flag){
int l=s1.size()+s2.size();
//cout<<l<<"\n";
if(l&1){
auto it=s1.end();
it--;
f[x]=*it;
}
else{
auto a=s1.end(); a--;
auto b=s2.begin();
//cout<<*a<<" "<<*b<<"\n";
f[x]=(*a+*b)/2;
}
}
if(x!=1){
auto it=s1.end(); it--;
if(*it<A[x]) s2.erase(s2.lower_bound(A[x]));
else s1.erase(s1.lower_bound(A[x]));
if(s1.size()<s2.size()){
auto th=s2.begin();
s1.insert(*th),s2.erase(th);
}
if(s1.size()-s2.size()==2){
auto th=s1.end(); th--;
s2.insert(*th),s1.erase(th);
}
}
}
inline void Solve(int x,int fa,int opt)
{
int res;
if(!opt) res=0;
else res=INF;
bool flag=false;
for(register int i=first[x];i;i=nex[i]){
if(v[i]==fa) continue;
flag=true;
Solve(v[i],x,opt^1);
if(!opt){ //A选
res=max(res,ans[v[i]]);
}
else res=min(res,ans[v[i]]);
}
if(flag) ans[x]=res;
else ans[x]=f[x];
}
signed main()
{
n=read();
for(register int i=1;i<=n;i++) A[i]=read();
for(register int i=1;i<n;i++){
int x=read(),y=read();
Add(x,y),Add(y,x);
}
DFS(1,0);
Solve(1,0,0);
printf("%lld\n",ans[1]);
return 0;
}