【洛谷6943】[ICPC2018 WF] Conquer The World(模拟费用流)
- 给定一棵\(n\)个点的树,每条边有一个长度。
- 第\(i\)个点上原本有\(x_i\)个人,需要有至少\(y_i\)个人,求所有人移动总路长的最小值。
- \(n\le2.5\times10^5,\sum y_i\le\sum x_i\le10^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\n",ans),0;
}
待到再迷茫时回头望,所有脚印会发出光芒