把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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
}
posted @ 2021-05-11 08:56  TheLostWeak  阅读(79)  评论(0编辑  收藏  举报