IOI2021集训队作业 207PE
一棵树,每个点有个权值\(v_i\)。
从根节点开始,每次遍历与走过的点联通的未走过的点,使得\(v_i\)的前缀和的最小值大于等于\(0\)。
问是否能够到达\(T\)。
\(n\le 10^5\)
经典贪心。。。然而由于不知道怎么处理终点而搞了几天。。。
可以将一个点看成\((a_i,b_i)\)的形式,表示前缀和的最小值为\(-a_i\),遍历了这个点可以得到\(b_i\)的收益。
如此定义\((a_i,b_i)\)比\((a_j,b_j)\)优:
- \(b_i\ge 0>b_j\)
- \(b_i,b_j\ge 0\and a_i<a_j\)
- \(b_i,b_j<0\and a_i+b_i>a_j+b_j\)
先求出每个点的初始状态,每次找到最优的,和它的父亲合并。
这样适用于遍历整棵树的情况。如果只要求到达\(T\),把\(b_T\)设为无限大即可。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200005
#define ll long long
#define INF 1000000000000000
int n,T;
struct EDGE{
int to;
EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
void link(int u,int v){
e[ne]={v,last[u]};
last[u]=e+ne++;
}
int fa[N];
void init(int x){
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa[x])
fa[ei->to]=x,init(ei->to);
}
int dsu[N];
int getdsu(int x){return dsu[x]==x?x:dsu[x]=getdsu(dsu[x]);}
struct Status{ll a,b;int id;} s[N];
bool cmp(Status son,Status fa){
if (fa.b>=0 && son.b>=0)
return fa.a<son.a;
if (fa.b<0 && son.b<0)
return fa.a+fa.b>son.a+son.b;
return fa.b>son.b;
}
Status q[N*2];
int nq;
void merge(int y,int x){
dsu[x]=y;
s[y].a=max(s[y].a,-s[y].b+s[x].a);
s[y].b=s[y].b+s[x].b;
}
int main(){
int Q;
scanf("%d",&Q);
while (Q--){
scanf("%d%d",&n,&T);
ne=0;
memset(last,0,sizeof(EDGE*)*(n+1));
for (int i=1;i<=n;++i){
int x;
scanf("%d",&x);
if (x<0)
s[i]={-x,x,i};
else
s[i]={0,x,i};
}
s[T].b=INF;
for (int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
link(u,v);
link(v,u);
}
init(1);
for (int i=1;i<=n;++i)
dsu[i]=i;
nq=0;
for (int i=2;i<=n;++i)
q[nq++]=s[i];
make_heap(q,q+nq,cmp);
while (nq){
Status x=q[0];
pop_heap(q,q+nq--,cmp);
if (s[x.id].a!=x.a || s[x.id].b!=x.b || getdsu(x.id)!=x.id)
continue;
int y=getdsu(fa[x.id]);
merge(y,x.id);
if (y!=1){
q[nq++]=s[y];
push_heap(q,q+nq,cmp);
}
else if (getdsu(T)==1)
break;
}
if (s[1].a>0)
printf("trapped\n");
else
printf("escaped\n");
}
return 0;
}