【洛谷6943】[ICPC2018 WF] Conquer The World(模拟费用流)

点此看题面

  • 给定一棵(n)个点的树,每条边有一个长度。
  • (i)个点上原本有(x_i)个人,需要有至少(y_i)个人,求所有人移动总路长的最小值。
  • (nle2.5 imes10^5,sum y_ilesum x_ile10^6)

模拟费用流

显然一个点上原有的(min{x_i,y_i})个人是不会动的。

因此(x_i>y_i)的情况可以视作有(x_i-y_i)只兔子,(x_i<y_i)的情况可以视作有(y_i-x_i)个洞。

由于这道题所有洞都必须匹配,需要给每个洞加上一个(-INF)的权值,使得选择所有洞一定最优。

我们在每个点合并它不同子树间的兔子和洞。

假设有兔子(x)和洞(y),当前点((LCA))为(z),它们之间的路径长度就是(d_x+d_y-2d_z)

由于当前点确定时(-2d_z)为定值,因此把兔子的权值(A_x)视为(d_x),洞的权值(B_y)视为(d_y-INF),然后分别开一个小根堆,每次取出各自的堆顶尝试更新答案即可。(更新答案的条件:(A_x+B_y-2d_z<0)

但我们还要考虑退流,如果我们想让兔子(x)能换成和另一个洞匹配,相当于新建一只权值为(2d_z-B_y)的兔子(x'),这两只兔子权值相加刚好消得只剩(A_x),除去了原本的洞的贡献。同理,想让洞(y)能换成和另一只兔子匹配,相当于新建一个权值为(2d_z-A_x)的洞(y')

因为这里的堆需要合并,写个左偏树即可。

代码:(O(VlogV))

#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 250000
#define LL long long
#define INF (LL)1e12
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
using namespace std;
int n,a[N+5],ee,lnk[N+5];struct edge {int to,nxt,v;}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;
class LeftistTree
{
	private:
		#define SZ 1000000
		int Nt,Rt[N+5];struct node {LL V;int D,S[2];}O[10*SZ+5];
		I int Merge(RI x,RI y)//合并
		{
			if(!x||!y) return x|y;O[x].V>O[y].V&&(swap(x,y),0);
			O[O[x].S[1]=Merge(O[x].S[1],y)].D>O[O[x].S[0]].D&&(swap(O[x].S[0],O[x].S[1]),0);
			return O[x].D=O[O[x].S[1]].D+1,x;
		}
	public:
		I LeftistTree() {O[0].D=-1;}I bool Empty(CI x) {return !Rt[x];}//初始化;判断是否为空
		I void Ins(CI x,Con LL& v) {O[++Nt].V=v,Rt[x]=Merge(Rt[x],Nt);}//增加一个新元素
		I void Union(CI x,CI y) {Rt[x]=Merge(Rt[x],Rt[y]);}//合并
		I void Pop(CI x) {Rt[x]=Merge(O[Rt[x]].S[0],O[Rt[x]].S[1]);}//弹出堆顶
		I LL Q(CI x) {return O[Rt[x]].V;}//询问堆顶值
}A,B;
LL ans;I void Calc(CI x,CI y,Con LL& d)//让x的兔子和y的洞配对
{
	LL a,b;W(!A.Empty(x)&&!B.Empty(y)&&(a=A.Q(x))+(b=B.Q(y))-2*d<0)//A[x]+B[x]-2*d<0
		ans+=a+b-2*d,A.Pop(x),B.Pop(y),A.Ins(x,2*d-b),B.Ins(y,2*d-a);//更新答案,新建兔子和洞以实现退流
}
I void dfs(CI x,CI lst,Con LL& d)
{
	W(a[x]>0) A.Ins(x,d),--a[x];W(a[x]<0) B.Ins(x,d-INF),ans+=INF,++a[x];//把兔子/洞加入堆中
	for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^lst&&(dfs(e[i].to,x,d+e[i].v),
		Calc(x,e[i].to,d),Calc(e[i].to,x,d),A.Union(x,e[i].to),B.Union(x,e[i].to),0);//合并不同子树间的兔子和洞
}
int main()
{
	RI i,x,y,z;for(read(n),i=1;i^n;++i) read(x,y,z),add(x,y,z),add(y,x,z);
	for(i=1;i<=n;++i) read(x),read(y),a[i]=x-y;return dfs(1,0,0),printf("%lld
",ans),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6943.html