zoukankan      html  css  js  c++  java
  • 【BZOJ2324】[ZJOI2011] 营救皮卡丘(费用流)

    点此看题面

    大致题意: 一张无向图,节点编号(0sim n)。在(0)号点有(k)个人,要求有人经过了(1sim i-1)的所有节点,才能经过节点(i)。求经过全部(n)个节点(k)个人走过的最小路径总长。

    网络流

    又是无向图,又是这么稀奇古怪的限制,这道题怎么看都不像是网络流啊。。。(不对,好像稀奇古怪的限制正是网络流的精神所在

    实际上,我们考虑对于某一个人,由TA首次经过的节点一定是一个递增序列。(是不是一下就有了方向?)

    而且,所有人首次经过的节点一定是不重复的(废话,不然还叫首次经过。。。),且所有人首次经过的节点拼在一起一定是(1sim n)的所有节点(这应该也显然)。

    考虑一个人当前首次经过了节点(x),接下来TA需要首次经过节点(y)

    因为在这道题中,一个人可以保持不动,而且题目也没有限制时间。所以,我们完全可以让TA傻站在那里,一直等到节点(x+1sim y-1)都被其他人首次经过。

    好,然后这个人就开始往节点(y)进军了。TA怎样走才是最优的呢?当然是走最短路啦!

    而此时节点(y+1sim n)依然不能行走,也就是说,TA只能经过节点(1sim y),从节点(x)走最短路到达节点(y)

    令这个最短路为(f_{x,y}),则它显然是可以用(Floyd)搞出来的。

    总结一下,我们对于每个点(x),向比它更大的点(y)连一条流量为(1),费用为(f_{x,y})的边,就把这个问题转化成网络流了。

    更具体的建图

    刚才我们已经确定了点与点之间的连边,现在我们要进一步讨论。

    显然根据先前提到的性质,每个点只能经过一次(不重复),且至少要经过一次(拼起来是(1sim n))。

    按照套路,把每个点拆成入点和出点,那么就相当于从入点向出点连一条上下界均为(1)的边。

    而这可以转化为,建立超级源/超级汇,然后从超级源向出点连一条流量(1)的边从入点向超级汇连一条流量(1)的边

    显然,之前点与点的连边就要变成出点与入点的连边

    而对于节点(0)这个特殊点,我们实际上不需要把它分成两个(因为它并没有下界限制),直接从超级源向它连一条流量(k)的边就可以了。

    具体实现详见代码。

    代码

    #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 150
    #define M 20000
    #define INF (int)1e9
    using namespace std;
    int n,m,t,f[N+5][N+5];
    class MinCostMaxlow//最小费用最大流
    {
    	private:
    		#define E(x) ((((x)-1)^1)+1)
    		int ee,lnk[2*N+5];struct edge {int to,nxt,F,C;}e[2*M+5];
    		int lst[2*N+5],IQ[2*N+5],F[2*N+5],C[2*N+5];queue<int> q;
    		I bool SPFA()
    		{
    			RI i,k;for(i=0;i<=2*n+2;++i) F[i]=C[i]=INF;q.push(S),C[S]=0;
    			W(!q.empty()) for(i=lnk[k=q.front()],q.pop(),IQ[k]=0;i;i=e[i].nxt)
    			{
    				if(!e[i].F||C[k]+e[i].C>=C[e[i].to]) continue;
    				C[e[i].to]=C[k]+e[lst[e[i].to]=i].C,F[e[i].to]=min(F[k],e[i].F),
    				!IQ[e[i].to]&&(q.push(e[i].to),IQ[e[i].to]=1);
    			}return F[T]^INF;
    		}
    	public:
    		I void Add(CI x,CI y,CI f,CI c)
    		{
    			e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f,e[ee].C=c,
    			e[++ee].nxt=lnk[y],e[lnk[y]=ee].to=x,e[ee].F=0,e[ee].C=-c;
    		}
    		int S,T;I void MCMF()
    		{
    			RI x,res=0;W(SPFA())
    			{
    				res+=F[T]*C[T],x=T;
    				W(x^S) e[lst[x]].F-=F[T],e[E(lst[x])].F+=F[T],x=e[E(lst[x])].to;
    			}printf("%d",res);
    		}
    }F;
    int main()
    {
    	RI i,j,k,x,y,z;scanf("%d%d%d",&n,&m,&t);
    	for(i=0;i<=n;++i) for(j=0;j<=n;++j) f[i][j]=INF;for(i=0;i<=n;++i) f[i][i]=0;//初始化
    	for(i=1;i<=m;++i) scanf("%d%d%d",&x,&y,&z),f[x][y]>z&&(f[x][y]=f[y][x]=z);//对于给定边修改距离
    	for(k=0;k<=n;++k) for(i=0;i<=n;++i) for(j=0;j<=n;++j)//Floyd
    		(i>k||j>k)&&f[i][k]+f[k][j]<f[i][j]&&(f[i][j]=f[i][k]+f[k][j]);//注意不能经过编号更大的点
    	for(i=0;i<=n;++i) for(j=i+1;j<=n;++j) F.Add(i?n+i:0,j,1,f[i][j]);//出点与入点间的连边
    	F.Add(F.S=2*n+1,0,t,0),F.T=2*n+2;//从超级源向0连边
    	for(i=1;i<=n;++i) F.Add(i,F.T,1,0),F.Add(F.S,n+i,1,0);//1~n节点和超级源汇的连边
    	return F.MCMF(),0;
    }
    
  • 相关阅读:
    Java面试题集(七)--Spring常见面试问题【重要】
    Java面试题集(六)
    qt4.8.4安装以及64位程序编译方法
    页面跳转动画设置方法
    Lua环境配置 windows + VS
    Oracle触发器(trigger):view,schema,database
    java课程设计(计算器)
    数据结构 练习 19-活动选择问题的实现(动态规划 和 贪心)
    网页在Safari快速滚动和回弹的原理: -webkit-overflow-scrolling : touch;的实现
    如何打开Nib文件
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ2324.html
Copyright © 2011-2022 走看看