题目
题目链接:https://atcoder.jp/contests/arc098/tasks/arc098_d
给出一个(N)个点(M)条边的无向连通图,每个点的标号为(1)到(n), 且有两个权值(A_i,B_i).第(i)条边连接了点(u_i)和(v_i).
最开始时你拥有一定数量的钱,并且可以选择这张图上的任意一个点作为起始点,之后你从这个点开始沿着给定的边遍历这张图。每当你到达一个点(v)时,你必须拥有至少(A_v)元。而当你到达了这个点后,你可以选择向它捐献(B_v)元(当然也可以选择不捐献),当然,你需要保证在每次捐献之后自己剩余的钱(geq 0)。
你需要对所有的(n)个点都捐献一次,求你一开始至少需要携带多少钱。
思路
可以得出一个结论:所有点一定都是在最后一次经过的时候才捐赠。假设两次经过点 (x) 的时间分别为 (t1,t2(t1<t2)),显然在 (t2) 时捐赠才能使得 ([t1,t2]) 时刻的钱尽量多。并且不会影响前后的钱。
令 (a_igets max(a_i-b_i,0)),显然最后一次经过 (i) 时(捐赠完之后),身上至少要有 (a_i) 元。为了让我们带的钱最少,我们一定是每次尽量经过 (a_i) 小的点去捐赠新的一个点。
这启发我们将点按照 (a) 从小到大排序,然后依次枚举点 (x) 以及它的出边 (x o y)。设 (z) 是 (y) 在重构树上的深度最小祖先,如果 (z) 排在 (x) 前面,那么就在重构树上从 (x) 向 (y) 连一条边。
这样我们得到了一棵重构树后,就可以在上面 dp 了。这棵重构树有一个优美的性质:深度越浅的点的 (a) 越大。和 Kruskal 重构树类似,但是没有虚点。
我们设 (f_x) 表示捐赠完 (x) 子树内所有点的最小代价。枚举 (x) 最后一次是往哪个儿子出发 (y),由于深度越浅的点的 (a) 越大且钱单调不增,所以新增的贡献只有可能来源于 (y) 的子树或者 (x)。
其中 (g_x) 表示 (x) 字数内 (b) 的和。
然后答案就是根的代价了。
时间复杂度 (O(nlog n))。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
const int N=1000010;
const ll Inf=7e18;
int n,m,a[N],b[N];
ll f[N],g[N];
struct edge
{
int next,to;
};
struct Tree
{
int tot,head[N];
edge e[N];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
int dfs(int x)
{
g[x]=b[x]; f[x]=Inf;
if (head[x]==-1)
{
f[x]=a[x]+b[x];
return g[x];
}
for (int i=head[x];~i;i=e[i].next)
g[x]+=dfs(e[i].to);
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
f[x]=min(f[x],g[x]-g[v]+max(1LL*a[x],f[v]));
}
return g[x];
}
}T;
bool cmp(int x,int y)
{
return a[x]<a[y];
}
struct Graph
{
int tot,father[N],id[N],rk[N],head[N];
edge e[N];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
int find(int x)
{
return x==father[x]?x:father[x]=find(father[x]);
}
void build()
{
for (int i=1;i<=n;i++)
id[i]=i,father[i]=i;
sort(id+1,id+1+n,cmp);
for (int i=1;i<=n;i++)
rk[id[i]]=i;
for (int i=1;i<=n;i++)
{
for (int j=head[id[i]];~j;j=e[j].next)
{
int v=find(e[j].to);
if (rk[v]<i)
{
T.add(id[i],v);
father[v]=id[i];
}
}
}
}
}G;
signed main()
{
memset(T.head,-1,sizeof(T.head));
memset(G.head,-1,sizeof(G.head));
scanf("%lld%lld",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%lld%lld",&a[i],&b[i]);
a[i]=max(a[i]-b[i],0LL);
}
for (int i=1,x,y;i<=m;i++)
{
scanf("%lld%lld",&x,&y);
G.add(x,y); G.add(y,x);
}
G.build(); T.dfs(G.id[n]);
printf("%lld
",f[G.id[n]]);
return 0;
}