[CERC2013] Escape
一、题目
二、解法
显然的思路是计算 \(f(u,i)\) 表示进入子树 \(u\) 时的生命值为 \(i\),最多赚取的生命值是多少。我们可以在终点 \(t\) 下面连接一个大小为 \(+\infty\) 的点,那么判断能否到 \(T\) 可以化归到判断 \(f(1,0)\geq +\infty\) 是否成立。
但是这东西并不好转移,因为你可能在生命值变化后反复进入一个子树。关键的 \(\tt observation\) 是:\(f(u,i)\) 关于 \(i\) 单调不降。那么我们可以转而维护 \(f(u,i)\) 的差分值,也就是对点 \(u\) 维护若干个二元组 \((x,y)\),表示如果生命值 \(\geq x\),就可以通过该子树额外赚取 \(y\) 的生命值。
发现子树的二元组是可以直接合并的,我们只需要考虑添加点 \(u\) 的影响,\(a[u]\geq 0\) 是平凡的,直接添加二元组 \((0,a[u])\)
如果 \(a[u]<0\),因为要维护赚取的性质所以不能直接添加。考虑要加入的二元组是 \((A,B)\),初始时 \(A=-a[u],B=a[u]\),我们取出 \(x\) 最小的 \((x,y)\),如果满足 \(x\leq A\) 或者 \(B<0\),那么把这两个二元组合并:
说明:考虑第一阶段的合并是因为 \(B<0\),由于我们进入这个子树是来赚钱的,所以要让 \(B>0\),赚钱的门槛也会随之提高(\(A\) 会变化,因为会新增条件 \(HP+B\geq x\));考虑第二阶段的合并是因为 \(x\leq A\),因为赚钱至少都要 \(A\),否则是赚不到钱的,那么要把门槛低的一些二元组给合并上来。
最后计算答案的时候,取出 \(x\) 最小的 \((x,y)\),如果 \(x\leq HP\) 就让 \(HP\leftarrow HP+y\),一直循环这个过程即可。
由于每次只会取出 \(x\) 最小的二元组,可以使用左偏树维护,时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 200005;
#define int long long
const int inf = 1e18;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,t,a[M],x[M],y[M],ls[M],rs[M],rt[M],d[M];
vector<int> g[M];
int merge(int u,int v)
{
if(!u || !v) return u+v;
if(x[u]>x[v]) swap(u,v);
rs[u]=merge(rs[u],v);
if(d[rs[u]]>d[ls[u]]) swap(ls[u],rs[u]);
d[u]=d[rs[u]]+1;
return u;
}
void dfs(int u,int fa)
{
for(int v:g[u]) if(v^fa)
{
dfs(v,u);
rt[u]=merge(rt[u],rt[v]);
}
if(a[u]>0)
{
x[u]=0;y[u]=a[u];d[u]=1;
rt[u]=merge(rt[u],u);
}
else
{
int A=-a[u],B=a[u];
while(rt[u] && (x[rt[u]]<=A || B<0))
{
A=max(A,x[rt[u]]-B);
B+=y[rt[u]];
rt[u]=merge(ls[rt[u]],rs[rt[u]]);
}
if(B>0)
{
x[u]=A;y[u]=B;d[u]=1;
rt[u]=merge(rt[u],u);
}
}
}
void work()
{
n=read();t=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
g[u].push_back(v);
g[v].push_back(u);
}
g[++n].push_back(t);
g[t].push_back(n);a[n]=inf;
dfs(1,0);t=0;
while(rt[1] && x[rt[1]]<=t)
{
t+=y[rt[1]];
rt[1]=merge(ls[rt[1]],rs[rt[1]]);
}
if(t>=inf) puts("escaped");
else puts("trapped");
for(int i=1;i<=n;i++)
{
g[i].clear();
x[i]=y[i]=ls[i]=rs[i]=rt[i]=d[i]=0;
}
}
signed main()
{
T=read();
while(T--) work();
}