【AT4144】[ARC098D] Donation(Kruskal重构树)
- 给定一张\(n\)个点\(m\)条边的无向图,到达一个点时至少要有\(A_i\)元,可以选择捐献\(B_i\)元。
- 可以从任意点出发,要求对于所有点都捐献一次,求一开始最少要带多少钱。
- \(n\le10^5\)
逆向思维
考虑倒过来,即我们到达某个点的时候至少要有\(A_i-B_i\)元,且第一次到达时可以获得\(B_i\)元。
\(Kruskal\)重构树
考虑当你能走到\(x\),就能到达\(A_x-B_x\ge A_y-B_y\)的整个连通块。
所以我们按照\(A_x-B_x\)建一棵\(Kruskal\)重构树(一开始智障按\(A_x\)建,调了半个小时)。
注意这里的权值在点上,和传统权值在边上有点不太一样,但总体都是贪心排序+并查集的思路。
即,我们按\(A_x-B_x\)从小到大排序,枚举每个点\(x\),枚举每条它连出的边\((x,y)\),如果\(y\)已经出现过,就在\(Kruskal\)重构树上由\(x\)向\(y\)所在连通块的根连边,并让\(x\)成为新的根。
树形\(DP\)
首先我们发现\(\sum B_i\)的代价是一定需要的,那么我们就\(DP\)除此之外的额外代价。
设\(f_x\)表示从\(x\)子树内开始把整棵子树走完所需的最少额外代价,\(g_x\)表示\(x\)子树内的\(B_i\)总和。
给\(f_x\)赋初值为\(\max\{A_x-B_x,0\}\),然后枚举一个子节点\(y\)转移:
\[f_x=\min\{f_x,\max\{f_y,A_x-B_x-g_y\}\}
\]
最后答案就是\(f_{rt}+g_{rt}\)。
代码:\(O(mlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LL long long
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
int n,m,id[N+5],A[N+5],B[N+5];I bool cmp(CI x,CI y) {return A[x]-B[x]<A[y]-B[y];}
int ee,lnk[N+5];struct edge {int to,nxt;}e[2*N+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
namespace K//Kruskal重构树
{
int rt,ee,lnk[N+5];struct edge {int to,nxt;}e[N+5];
int fa[N+5],Ex[N+5];I int getfa(CI x) {return fa[x]?fa[x]=getfa(fa[x]):x;}//并查集
I void Build() {for(RI i=1,j,x,y;i<=n;Ex[x]=1,++i)//建树
for(j=::lnk[x=id[i]];j;j=::e[j].nxt) Ex[y=getfa(::e[j].to)]&&(add(x,y),fa[y]=x);}
LL f[N+5],g[N+5];I void dfs(CI x=id[n],CI lst=0)//树形DP
{
f[x]=max(A[x]-B[x],0),g[x]=B[x];for(RI i=lnk[x];i;i=e[i].nxt)
dfs(e[i].to,x),g[x]+=g[e[i].to],f[x]=min(f[x],max(f[e[i].to],A[x]-B[x]-g[e[i].to]));//DP转移
}
}
int main()
{
RI i,x,y;for(read(n,m),i=1;i<=n;++i) read(A[i],B[i]),id[i]=i;sort(id+1,id+n+1,cmp);//按A[i]-B[i]排序
for(i=1;i<=m;++i) read(x,y),add(x,y),add(y,x);
return K::Build(),K::dfs(),printf("%lld\n",K::f[id[n]]+K::g[id[n]]),0;//最终答案为根节点f+g
}
待到再迷茫时回头望,所有脚印会发出光芒