• 【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
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    厕所惊魂
    感谢协助学生返校,邀请交警合影留念
    小丑杀人
    东芝Toshiba e-STUDIO打印身份证
    JavaScript
    html
    pymysql模块
    线程
    队列与进程池
    网络编程
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AT4144.html
Copyright © 2020-2023  润新知