【AT4144】[ARC098D] Donation(Kruskal重构树)

点此看题面

  • 给定一张(n)个点(m)条边的无向图,到达一个点时至少要有(A_i)元,可以选择捐献(B_i)元。
  • 可以从任意点出发,要求对于所有点都捐献一次,求一开始最少要带多少钱。
  • (nle10^5)

逆向思维

考虑倒过来,即我们到达某个点的时候至少要有(A_i-B_i)元,且第一次到达时可以获得(B_i)元。

(Kruskal)重构树

考虑当你能走到(x),就能到达(A_x-B_xge 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
",K::f[id[n]]+K::g[id[n]]),0;//最终答案为根节点f+g
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/AT4144.html