POJ 1459

转载请注明出处:優YoU http://user.qzone.qq.com/289065406/blog/1299339754

 

提示:BFS找增广链 + 压入重标法

解题思路:

多源多汇最大流问题

 

题目给出很多都是废话,特别是符号s(u),d(u),Con还有那条公式都别管,混淆视听

难点在于构图

 

电站p(u)均为源点,用户c(u)均为汇点,中转站当普通点处理

 

第一个误区是例图, 结点 和 边 都有x/y(流量和容量),这个很容易使人产生矛盾(因为学习最大流问题是,只有 边 才有流量和容量。

     但是不难发现,题目所给的例图中有多个源点,多个汇点,多个普通点,只有源点和汇点才标有 x/y,普通点没有标x/y,而且所给出的所有边都有x/y。 这无疑在促使我们对图做一个变形: 建议一个超级源s,一个超级汇t,使s指向所有源点,并把源点的 容量y 分别作为这些边的 容量,使所有汇点指向t,并把汇点的容量y分别作为这些边的 容量,然后本来是源点和汇点的点,全部变为普通点。这样就把“多源多汇最大流”变形为“单源单汇最大流”问题。

第二个误区就是流量值。 学习最大流问题时,会发现边上的流量值是给定初始值的,但是这题的输入只有容量,没有流量,很多人马上感觉到无从入手。其实边上的流量初始值为多少都没有所谓,解最大流需要用到的只有容量。但是一般为了方便起见, 会把所有边的流量初始化为0。这样做有一个最大的好处,就是可以回避 反向弧 的存在,这个下面详细叙述。

 

本题中要注意的是:

1、  如果输入中,某一点上有环,就无视掉。环是否存在不影响最终结果。

2、  一般两点之间都是单边,一旦存在双边也没有问题,因为由定义知两个方向的容量一定相等(其实不相等也无妨,因为其中有一条为 反向弧,前面已经提到 反向弧 是可以直接回避、无视的,因此反向弧上的容量为多少就不重要了),而且在寻找增广路的标号过程中,搜索的是未标号的点,就是说(u,v)这条弧即使是双向的,但一旦从u到达v后,就不能回头了,因为两者都被标记了,即另外一条弧就不起任何作用了。

 

下面详细说说为什么能够回避反向弧。

首先需要明确,任意一个点j上记录的信息有:

1、  寻找增光路时,除超级源s外,增广路上任一点j都有一个唯一的前驱i(i被记录在j)

2、  min{从i到j的容流差,l(vi)}

3、  构图时,除超级汇t外,图上任一点j都会直接指向一些点(这些点作为后继点,同在记录在j)

 

从这个特点可以知道,从超级源开始寻找增广路时,万一遇到双向边,正向弧,反向弧自动被回避。万一遇到单向边,如果是非饱和正向弧,就会继续走下去;如果是反向弧,这条弧必然是 零弧(每条边初始化流量均为0),定义知如果增广路有反向弧,它必须要是非零弧,而且由于反向弧每次都不会经过,所以在改进增广路时反向弧上的流量也不会被改变,永远为0,也就与最终结果无关了

 

最后当无法寻找增广路时,最大流就是与超级源s直接关联的边上的 流量之和

 

 

  1. /*BFS+压入重标法*/  
  2.   
  3. //Memory  Time   
  4. //384K    860MS    
  5.   
  6. #include<iostream>  
  7. using namespace std;  
  8.   
  9. const int inf=10001;  
  10.   
  11. int n;  //总节点数  
  12. int np; //电站数  
  13. int nc; //用户数  
  14. int line;  //线路数  
  15. int cap[102][102];  //弧(u,v)的容量  
  16. int flow[102][102];  //弧(u,v)的流量  
  17. bool vist[102];   //标记点v是否已标号  
  18. int s,t;  //超级源,超级汇  
  19.   
  20. class info   //当前点v的标记信息  
  21. {  
  22. public:  
  23.     int pre;  //当前点v的前驱u  
  24.     int lv;  //l(v)  
  25.     int nei[101];  //当前结点直接指向的邻居结点  
  26.     int pn;  //邻居结点的指针  
  27. }node[102];  
  28.   
  29. int min(int a,int b)  
  30. {  
  31.     return a<b?a:b;  
  32. }  
  33.   
  34. void back(void)  
  35. {  
  36.     int x=t;  
  37.     while(x!=s)  
  38.     {  
  39.         flow[ node[x].pre ][x] += node[t].lv;  //改进增广路  
  40.         x=node[x].pre;  
  41.   
  42.     }  
  43.     return;  
  44. }  
  45.   
  46. bool bfs(void)  
  47. {  
  48.     memset(vist,false,sizeof(vist));  
  49.     node[s].pre=-1;  
  50.     node[s].lv=inf;  
  51.     vist[s]=true;  
  52.   
  53.     int queue[102];  
  54.     int head=0;  
  55.     int tail=0;  
  56.     queue[tail++]=s;  
  57.   
  58.     while(head<=tail-1)  //注意,这是再也找不到增广路的结束条件  
  59.     {  
  60.         int x=queue[head];  
  61.         int y;  
  62.         for(int i=0;i<node[x].pn;i++)  
  63.         {  
  64.             y=node[x].nei[i];  
  65.             if(!vist[y] && flow[x][y]<cap[x][y])   //搜索的目标要求是 未标记 & 非饱和弧  
  66.             {  
  67.                 queue[tail++]=y;  
  68.   
  69.                 vist[y]=true;  
  70.                 node[y].pre=x;  
  71.                 node[y].lv=min( node[x].lv , cap[x][y]-flow[x][y] );  
  72.             }  
  73.             if(vist[t])  //当超级汇被标记  
  74.                 break;  
  75.         }  
  76.         if(!vist[t])  
  77.             head++;  
  78.         else  
  79.             return true;  //搜索到一条增广路  
  80.     }  
  81.     return false;  
  82. }  
  83.   
  84. int main(int i,int j,int u,int v,int z,char temp)  
  85. {  
  86.     while(cin>>n>>np>>nc>>line)  
  87.     {  
  88.         /*Initial*/  
  89.   
  90.         s=n;  
  91.         t=n+1;  
  92.         for(i=0;i<n+1;i++)  
  93.             node[i].pn=0;  
  94.   
  95.         /*Input & Structure Maps*/  
  96.   
  97.         for(i=1;i<=line;i++)  
  98.         {  
  99.             cin>>temp>>u>>temp>>v>>temp>>z;  
  100.             if(u==v)  
  101.                 continue;   //不需要环  
  102.             cap[u][v]=z;  
  103.             flow[u][v]=0;   //每条边的流量都初始化为0  
  104.             node[u].nei[ node[u].pn++ ]=v;  
  105.         }  
  106.         for(i=1;i<=np;i++)  
  107.         {  
  108.             cin>>temp>>v>>temp>>z;  
  109.             cap[s][v]=z;     //建立超级源,指向所有电站  
  110.             flow[s][v]=0;  
  111.             node[s].nei[ node[s].pn++ ]=v;  
  112.         }  
  113.         for(i=1;i<=nc;i++)  
  114.         {  
  115.             cin>>temp>>u>>temp>>z;  
  116.             cap[u][t]=z;     //建立超级汇,被所有用户指向  
  117.             flow[u][t]=0;  
  118.             node[u].nei[ node[u].pn++ ]=t;  
  119.         }  
  120.   
  121.         /*标号法找增广轨*/  
  122.   
  123.         while(true)  
  124.         {  
  125.             if(bfs())  //如果能搜到到增广路  
  126.                 back();  //从超级汇开始回溯,改进增广路  
  127.             else  
  128.             {  
  129.                 int max=0;        //输出最大流  
  130.                 for(i=0;i<node[s].pn;i++)  
  131.                     max+=flow[s][ node[s].nei[i] ];  
  132.                 cout<<max<<endl;  
  133.                 break;  
  134.             }  
  135.         }  
  136.     }  
  137.     return 0;  

本人喜爱版本:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define M 105
#define INF 900000000
int cap[M][M];
int flow[M][M];
int queue[M];
int link[M],l[M];
int n,np,nc,line;
bool vist[M];
int s,t;

void read()
{
	memset(cap,-1,sizeof(cap));

	int i,u,v,z;
	char temp;
	for(i=0;i<line;i++)
	{
		cin>>temp>>u>>temp
			>>v>>temp>>z;
		if(u==v)
		{
			continue;
		}
		cap[u][v]=z;
	}

	for(i=0;i<np;i++)
	{
		cin>>temp>>u>>temp>>z;
		cap[s][u]=z;
	}

	for(i=0;i<nc;i++)
	{
		cin>>temp>>u>>temp>>z;
		cap[u][t]=z;
	}
}

void back()
{
	int x=t;
	while(x!=s)
	{
		flow[ link[x] ][x] +=l[t];
		x=link[x];
	}
}

bool bfs()
{
	memset(vist,0,sizeof(vist));
	l[s]=INF;
	vist[s]=true;

	int head=0;
	int tail=0;
	queue[tail++]=s;
	
	int i,x;
	while(head<tail)
	{
		x=queue[head];
		
		for(i=0;i<=t;i++)
		{
			if(cap[x][i]!=-1&&!vist[i]&&flow[x][i]<cap[x][i])
			{
				queue[tail++]=i;

				vist[i]=true;
				link[i]=x;
				l[i]=min( l[x],cap[x][i]-flow[x][i] );
			}
			if(vist[t])
			{
				return true;
			}
		}
		head++;
	}
	return false;
}

void cal()
{
	memset(flow,0,sizeof(flow));
	while(true)
	{
		if(bfs())
		{
			back();
		}
		else
		{
			break;
		}
	}
}

void answer()
{
	int i,ans=0;
	for(i=0;i<=t;i++)
	{
		if(cap[s][i]!=-1)
		{
			ans+=flow[s][i];
		}
	}
	printf("%d\n",ans);
}

int main()
{
	while(~scanf("%d%d%d%d",&n,&np,&nc,&line))
	{
		s=n;
		t=n+1;
		read();
		cal();
		answer();
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值