• 模拟费用流的基本模型


    Preface

    在本蒟蒻学习的过程中参考了这位这位dalao的博客

    费用流,是OI中解决最优化最优化问题的一个常用算法。但众所周知费用流的模型虽然很容易构建,但他的时间效率却比较低下

    模拟费用流方法是指利用除费用流以外的手段解决一些费用流问题。一般来说,一个问题如果使用模拟费用流算法来解决,你在整个代码中不会见到任何一个与费用流有关的片段。可以说,这个方法非常的抽象

    而在这一个算法的学习过程中,也存在着一些比较实用的模型。通过对这些模型的分析让我们可以对模拟费用流的理解步步加深


    引子

    你现在正在处理一个问题:

    在一条数轴上有一些兔子和一些洞,其中兔子的坐标为(x_i),洞的坐标为(y_i)。兔子只能向左走,现在你要最小化所有兔子行走的距离之和。

    对于这个问题,很显然我们按坐标排序后从左到右操作,每次遇到兔子的时候考虑找到它前面的最近的未匹配的洞

    用一个就可以维护上面的操作


    模型一

    在之前的基础上,洞有了附加权值(v_i),这意味着如果要选择这个洞那么必须多付出(v_i)的代价(相当于打开洞门?)。还是求最小的代价之和。

    还是和之前一样,从左往右考虑每个兔子和洞

    如果当前这个位置是一个兔子(a),那么让它进入之前的一个洞(b)的代价就是(x_a-y_b+v_b),这也就意味着我们要取(-y_b+v_b)最小的洞,可以用一个来存储所有的洞

    但我们发现这个贪心的想法只局限于眼下,可能最优的解是另一个兔子(c)来和洞(b)匹配,然后(a)不匹配。那么我们考虑去掉(a)对答案的影响,那么就是要减去它的影响(x_a)

    那么怎么处理呢,很简单,我们选择了(a)之后就向堆里扔进去一个权值为(-x_a)的洞。这样选择了这个洞就意味这进行了反悔操作

    注意这里我们取消(a,b)的匹配,改为(c,b)的匹配,这个操作是不是很像费用流的退流操作,因此实际上模拟费用流的本质就是用其他的东西来模拟退流,也就是说我们要考虑怎么在贪心的基础上实现反悔

    好了知道了这个模型之后你就可以去做一道板题了:BZOJ 4977 跳伞求生,就是把这里的最小值改成最大值,维护的方法完全类似

    #include<cstdio>
    #include<queue>
    #include<algorithm>
    #define RI register int
    #define CI const int&
    using namespace std;
    const int N=100005;
    struct data
    {
        int pos,val;
        friend inline bool operator < (const data& A,const data& B)
        {
            return A.pos!=B.pos?A.pos<B.pos:!~A.val;
        }
    }a[N<<1]; int n,m; priority_queue <int> hp; long long ans;
    int main()
    {
        RI i; for (scanf("%d%d",&n,&m),i=1;i<=n;++i)
        scanf("%d",&a[i].pos),a[i].val=-1;
        for (i=1;i<=m;++i) scanf("%d%d",&a[n+i].pos,&a[n+i].val);
        for (sort(a+1,a+n+m+1),i=1;i<=n+m;++i) if (!~a[i].val)
        {
            if (hp.empty()||hp.top()+a[i].pos<=0) continue;
            ans+=hp.top()+a[i].pos; hp.pop(); hp.push(-a[i].pos);
        } else hp.push(a[i].val-a[i].pos);
        return printf("%lld",ans),0;
    }
    

    模型二

    在之前(或之后)的任意一种模型中,每个兔子必须找到洞来匹配。

    考虑在开始时就让每一个兔子都与一个距离它(infty)的洞匹配,这样这个匹配就无法退流


    模型三

    兔子洞有额外权值(v_i),且兔子也有额外权值(w_i)。兔子可以向左右两边走。

    注意,这是所有模型中可以说是最重要的一个了。下面的模型都和它密切相关,一定要掌握透彻

    还是和上面的分析类似,我们仍旧是从左向右考虑这个过程,并且还要考虑兔子和后面的洞匹配所带来的一系列情况

    • 考虑一只兔子(b)如果选择了洞(a),贡献就是(v_a+x_b)。如果之后的一个洞(c)替换了这个洞,总贡献应该加上(y_c-v_a-2x_b),因此我们可以加入一个权值为(-v_a-2x_b)的兔子
    • 考虑一个洞(b)如果找到了兔子(a),贡献就是(w_a+y_b+s_b)。之后如果有一个兔子(c)抢走了这个洞,总贡献应该加上(w_c+x_c-w_a-2y_b),对于(c)来说,它找到了一个洞,而(a)也不会因此无家可归。因为当我们处理(a)的时候,我们肯定为它分配好了在它前面的洞,后来(b)找到了(a)然后(a)被赶走了,那么显然(a)可以回去找之前分配给它的洞,赶走里面待着的兔子,然后以此类推。这样,一切有回到了(b)没有发现(a)的样子,因此对于(c)来说,我们可以新增一个权值为(-w_a-2y_b)的洞
    • 考虑一个洞(b)如果找到了兔子(a),贡献就是(w_a+y_b+s_b)。之后如果有一个洞(c)替换了这个洞,总贡献应该加上(y_c+s_c-y_b-s_b),对于(c)来说,它找到了一个权值为(-y_b-s_b)的兔子

    然后,我们发现尤其是在第二种情况中,这种(c)抢走了(b)(a)就去匹配它本来匹配过的(d),然后现在匹配(d)的点又去……的操作,是不是就是费用流的推流操作(前面的只退一次,这里就更复杂了)

    这时,兔子已经不在是兔子,洞也已经不再是洞了。洞和兔子因为彼此的利益关系交织在一起,但是无论如何,我们只需要一个堆就可以把它们处理地服服帖帖的

    因此,关键的关键还是通过新增了一些“洞” 和 “兔子” 完成了两者的 “反悔” 操作


    模型四

    我们在以上的任意模型中,兔子和洞都有分身(即一个位置上会有很多个兔子和洞),记为(c_i,d_i)。求最小的代价和

    我们可以把分身看做有许多个单独的情况。但是当分身的数目很多(到(10^9)级别)的时候就不能这么搞了

    考虑直接把相同的兔子和洞合并在一起,用一个pair绑起来然后用上面的方法做即可

    什么?你说合并的时候产生的新兔子和新洞的数目可能会很多?这个貌似可以用匹配不交叉来证,但是我太弱了不会。不过你只要知道新添加的点都是常数级别的就好了

    那么接下来就可以去做一道题了:UOJ#455 雪灾与外卖

    #include<cstdio>
    #include<queue>
    #include<algorithm>
    #include<iostream>
    #define RI register int
    #define CI const int&
    using namespace std;
    typedef long long LL;
    const int N=100005;
    const LL INF=1e12;
    struct event
    {
    	int pos,num,val;
    	friend inline bool operator < (const event& A,const event& B)
    	{
    		return A.pos<B.pos;
    	}
    }a[N<<1]; int n,m;
    struct data
    {
    	LL val; int num;
    	inline data(LL Val=0,CI Num=0)
    	{
    		val=Val; num=Num;
    	}
    	friend inline bool operator < (const data& A,const data& B)
    	{
    		return A.val>B.val;
    	}
    }; priority_queue <data> A,B; long long sum,ans; //A-Holes B-Rabbits
    int main()
    {
    	//freopen("ex_hole7.in","r",stdin);
    	RI i; for (scanf("%d%d",&n,&m),i=1;i<=n;++i)
    	scanf("%d",&a[i].pos),a[i].num=1,a[i].val=-1;
    	for (i=1;i<=m;++i)
    	scanf("%d%d%d",&a[n+i].pos,&a[n+i].val,&a[n+i].num),sum+=a[n+i].num;
    	if (sum<n) return puts("-1"),0; sort(a+1,a+n+m+1);
    	for (A.push(data(INF,n)),i=1;i<=n+m;++i) if (!~a[i].val)
    	{
    		data tp=A.top(); A.pop(); ans+=tp.val+a[i].pos; --tp.num;
    		if (tp.num) A.push(tp); B.push(data(-tp.val-2LL*a[i].pos,1));
    	} else
    	{
    		int left=a[i].num,cs=0; while (left&&!B.empty())
    		{
    			data tp=B.top(); if (tp.val+a[i].pos+a[i].val>=0) break;
    			B.pop(); int cur=min(left,tp.num); tp.num-=cur; left-=cur;
    			cs+=cur; ans+=(tp.val+a[i].pos+a[i].val)*cur;
    			if (tp.num) B.push(tp); A.push(data(-tp.val-2LL*a[i].pos,cur)); 
    		}
    		if (cs) B.push(data(-a[i].pos-a[i].val,cs));
    		if (left) A.push(data(a[i].val-a[i].pos,left));
    	}
    	return printf("%lld",ans),0;
    }
    

    模型五

    在模型四的基础上,分身必须匹配至少一个

    我们把分身拆成两种,一种有一个,匹配了可以产生额外价值(-infty),另一种有(c_i-1)个,匹配后产生额外价值(0)


    模型六

    把兔子和兔子洞搬到树上。两点间距离定义为树上距离。

    还是考虑先定下一种方向,那么我们从底向上匹配,每次合并一个点不同子树的东西,把堆改为可并堆即可

    然后又是一道题:LOJ#6405 征服世界

    #include<cstdio>
    #include<utility>
    #include<iostream>
    #define RI register int
    #define CI const int&
    #define CL const LL&
    #define mp make_pair
    #define fi first
    #define se second
    using namespace std;
    typedef long long LL;
    typedef pair <LL,int> pi;
    const int N=250005;
    const LL INF=1e12;
    struct edge
    {
    	int to,nxt,v;
    }e[N<<1]; int n,head[N],u,v,c,x[N],y[N],cnt; LL ans,dis[N];
    class Lefty_Tree
    {
    	private:
    		struct data
    		{
    			int ch[2],dis; pi v;
    		}node[N*20]; int rt[N*20],tot;
    		#define lc(x) node[x].ch[0]
    		#define rc(x) node[x].ch[1]
    		#define D(x) node[x].dis
    		#define V(x) node[x].v
    		inline int merge(int x,int y)
    		{
    			if (!x||!y) return x|y; if (V(x)>V(y)) swap(x,y);
    			rc(x)=merge(rc(x),y); if (D(rc(x))>D(lc(x))) swap(lc(x),rc(x));
    			D(x)=D(rc(x))+1; return x;
    		}
    	public:
    		inline void insert(CI pos,CL val,CI num)
    		{
    			V(rt[pos]=++tot)=mp(val,num); D(tot)=1;
    		}
    		inline bool non_empty(CI x)
    		{
    			return rt[x];
    		}
    		inline void Union(CI x,CI y)
    		{
    			rt[x]=merge(rt[x],rt[y]);
    		}
    		inline pi top(CI x)
    		{
    			return V(rt[x]);
    		}
    		inline void remove(CI x)
    		{
    			rt[x]=merge(lc(rt[x]),rc(rt[x]));
    		}
    		inline void updata(CI x,CI y)
    		{
    			V(rt[x]).se+=y;
    		}
    		#undef lc
    		#undef rc
    		#undef D
    		#undef V
    }X,Y; int totx,toty;
    inline void addedge(CI x,CI y,CI z)
    {
    	e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
    	e[++cnt]=(edge){x,head[y],z}; head[y]=cnt;
    }
    inline void Union(CI x,CI y,CL d)
    {
    	while (X.non_empty(x)&&Y.non_empty(y))
    	{
    		pi tx=X.top(x),ty=Y.top(y);
    		if (tx.fi+ty.fi-(d<<1LL)>=0) break;
    		int cur=min(tx.se,ty.se); ans+=(tx.fi+ty.fi-(d<<1LL))*cur;
    		X.updata(x,-cur); Y.updata(y,-cur);
    		if (!(tx.se-cur)) X.remove(x); if (!(ty.se-cur)) Y.remove(y);
    		X.insert(++totx,(d<<1LL)-ty.fi,cur); X.Union(x,totx);
    		Y.insert(++toty,(d<<1LL)-tx.fi,cur); Y.Union(y,toty);
    	}
    }
    #define to e[i].to
    inline void DFS(CI now=1,CI fa=0)
    {
    	if (x[now]) X.insert(now,dis[now],x[now]);
    	if (y[now]) Y.insert(now,dis[now]-INF,y[now]),ans+=INF*y[now];
    	for (RI i=head[now];i;i=e[i].nxt) if (to!=fa)
    	{
    		dis[to]=dis[now]+e[i].v; DFS(to,now);
    		Union(now,to,dis[now]); Union(to,now,dis[now]);
    		X.Union(now,to); Y.Union(now,to);
    	}
    }
    #undef to
    int main()
    {
    	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
    	RI i; for (scanf("%d",&n),i=1;i<n;++i) scanf("%d%d%d",&u,&v,&c),addedge(u,v,c);
    	for (i=1;i<=n;++i) scanf("%d%d",&x[i],&y[i]),c=min(x[i],y[i]),x[i]-=c,y[i]-=c;
    	return totx=toty=n,DFS(),printf("%lld",ans),0;
    }
    

    Postscript

    这就完了?其实才刚刚开始呢……

  • 相关阅读:
    redis性能优化、内存分析及优化
    代码质量审核和管理工具分析比较
    SpringBoot集成Nacos
    Navicat,Dbeaver,heidiSql,DataGrip数据库连接工具比较
    python报错:
    6.Python深入_内存管理
    Win7安装python第三方模块objgraph报错
    5.Python深入_装饰器
    4.Python深入_闭包
    1.Python深入_对象的属性
  • 原文地址:https://www.cnblogs.com/cjjsb/p/11732212.html
Copyright © 2020-2023  润新知