zoukankan      html  css  js  c++  java
  • 【洛谷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;
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    剑指Offer-11.二进制中1的个数(C++/Java)
    剑指Offer-10.矩形覆盖(C++/Java)
    剑指Offer-9.变态跳台阶(C++/Java)
    UVA 1608 Non-boring sequence 不无聊的序列(分治,中途相遇)
    UVA1607 Gates 与非门电路 (二分)
    UVA 1451 Average平均值 (数形结合,斜率优化)
    UVA 1471 Defense Lines 防线 (LIS变形)
    UVA 1606 Amphiphilic Carbon Molecules 两亲性分子 (极角排序或叉积,扫描法)
    UVA 11134 FabledRooks 传说中的车 (问题分解)
    UVA 1152 4 Values Whose Sum is Zero 和为0的4个值 (中途相遇)
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6943.html
Copyright © 2011-2022 走看看