poj 2315 最小费用最大流入门题

增加一个超级源点和超级汇点,容量为2,费用为0。

有重边,且为无向边。所以要用邻接表,需要建4条边。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<string>
#include<map>
#include<set>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<sstream>
#define LL long long
#define OJ_PRINT 0
#define READ_FILE 0
using namespace std;
const int NN_MAX = 1100;
const int MM_MAX = 10000+10;
const int INF = 0x1fffffff;
struct Edge{
  int from,to,cap,flow,cost;
  Edge (int a1=0,int b1=0,int c1=0,int d1=0,int e1=0) : from(a1),to(b1),cap(c1),flow(d1),cost(e1){}
};
/**********************************************************/
int n,m,s,t;
int mcost,cnt;
Edge theEdge[MM_MAX*4];
vector<int> G[NN_MAX];
int d[NN_MAX],p[NN_MAX],a[NN_MAX];
bool inqee[NN_MAX];
/**********************************************************/
int min_2 (int x,int y) {return x<y?x:y;}
int max_2 (int x,int y) {return x>y?x:y;}
bool mcmf ();
void Init ();
/**********************************************************/
int main()
{
  if (READ_FILE) freopen ("in.txt","r",stdin);
  while(scanf ("%d %d",&n,&m)!=EOF)
  {
	Init ();
	int a,b,c;
	cnt=0;
	for (int i = 0; i < m; i++){
	  scanf ("%d%d%d",&a,&b,&c);//无向边,两个正反,共四个边
	  theEdge[cnt++]=Edge (a,b,1,0,c);G[a].push_back (cnt-1);
	  theEdge[cnt++]=Edge (b,a,0,0,-c);G[b].push_back (cnt-1);
	  theEdge[cnt++]=Edge (b,a,1,0,c);G[b].push_back (cnt-1);
	  theEdge[cnt++]=Edge (a,b,0,0,-c);G[a].push_back (cnt-1);
	}
	theEdge[cnt++]=Edge (0,1,2,0,0); G[0].push_back (cnt-1);//0 n+1是超级起点和超级终点
	theEdge[cnt++]=Edge (1,0,0,0,0); G[1].push_back (cnt-1);
	theEdge[cnt++]=Edge (n,n+1,2,0,0); G[n].push_back (cnt-1);
	theEdge[cnt++]=Edge (n+1,n,0,0,0); G[n+1].push_back (cnt-1);
	s=0,t=n+1;
	mcost=0;
	while( mcmf() ) ;
	printf ("%d\n",mcost);
  }
  return 0;
}
void Init ()
{
  for (int i=0;i<=n+1;i++)
	G[i].clear ();
}
bool mcmf ()
{
  for (int i=0;i<=n+1;i++) d[i]=INF;
  memset (inqee,false,sizeof (inqee));
  d[s]=0;
  inqee[s]=1;p[s]=s;a[s]=INF;
  queue<int> qee;
  qee.push (s);
  while (!qee.empty ())
  {
	int x=qee.front ();qee.pop ();
	inqee[x]=0;//坑爹啊,就是这个错误卡了我两天,一个点可能进队多次
	for (int i=0;i<G[x].size ();i++){
	  Edge e=theEdge[G[x][i]];
	  if (e.cap>e.flow && d[e.to]>d[x]+e.cost){//下一个点符合条件
		d[e.to]=d[x]+e.cost;//最小费用
		p[e.to]=G[x][i];
		a[e.to]=min_2 (a[x],e.cap-e.flow);//最大流
		if (!inqee[e.to]){
		  qee.push (e.to);
		  inqee[e.to]=1;
		}
	  }
	}
  }
  if (d[t]==INF) return false;
  mcost+=d[t]*a[t];
  for (int i=t;i!=s;i=theEdge[p[i]].from){
	theEdge[p[i]].flow+=a[t];
	theEdge[p[i]^1].flow-=a[t];
  }
  return true;
}

邻接表的实现也可以在通过在Edge结构体中加入next变量,保存的是和它邻接的theEdge的下标。head[]数组保存的是某点最后一次出现在theEdge的下标。

例如,对于输入数据

4 5
2 4 2
1 2 1
3 4 1
2 3 1
1 3 2
则结构体theEdge内容是

i-from-to-cap-cost-next
 0-2 4 1  2 -1
 1-4 2 0 -2 -1
 2-4 2 1  2  1
 3-2 4 0 -2  0
 4-1 2 1  1 -1
 5-2 1 0 -1  3
 6-2 1 1  1  5
 7-1 2 0 -1  4
 8-3 4 1  1 -1
 9-4 3 0 -1  2
10-4 3 1  1  9
11-3 4 0 -1  8
12-2 3 1  1  6
13-3 2 0 -1 11
14-3 2 1  1 13
15-2 3 0 -1 12
16-1 3 1  2  7
17-3 1 0 -2 14
18-3 1 1  2 17
19-1 3 0 -2 16
20-0 1 2  0 -1
21-1 0 0  0 19
22-4 5 2  0 10
23-5 4 0  0 -1
head数组中的内容是:

 0  1  2  3  4  5
 20 21 15 18 22 23
在for (int i=head[x];i!=-1;i=theEdge[i].next)循环中,首先x=0,取出head[0]=20,在theEdge[20]中将1入队,再取出head[1]=21(0)→19(3)→16(3)→7(2)→4(2)→-1,可以看出依次将邻接的边都取到了。

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<string>
#include<map>
#include<set>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<sstream>
#define LL long long
#define OJ_PRINT 0
#define READ_FILE 0
using namespace std;
const int NN_MAX = 1100;
const int MM_MAX = 10000+10;
const int INF = 0x1fffffff;
struct Edge{
  int from,to,cap,cost,next;//next 相当于邻接表下标
  Edge (int a1=0,int b1=0,int c1=0,int d1=0,int e1=0):from(a1),to(b1),cap(c1),cost(d1),next(e1) {}
};
/**********************************************************/
int n,m,s,t;
int mcost,cnt;
Edge theEdge[MM_MAX*4];
int head[NN_MAX];
//d[]用于求最小费用路径长度,即最短路。a[]用于保存最大流,d[i]*a[i]即是最小费用最大流
//p[]保存的是theEdge[]下标
int d[NN_MAX],p[NN_MAX],a[NN_MAX];
bool inqee[NN_MAX];
/**********************************************************/
int min_2 (int x,int y) {return x<y?x:y;}
int max_2 (int x,int y) {return x>y?x:y;}
bool mcmf ();
/**********************************************************/
int main()
{
  if (READ_FILE) freopen ("in.txt","r",stdin);
  while(scanf ("%d %d",&n,&m)!=EOF)
  {
	int a,b,c;
	cnt=0;
	memset (head,-1,sizeof (head));
	for (int i = 0; i < m; i++){
	  scanf ("%d%d%d",&a,&b,&c);//无向边,两个正反,共四个边
	  theEdge[cnt]=Edge (a,b,1,c,head[a]);head[a]=cnt++;
	  theEdge[cnt]=Edge (b,a,0,-c,head[b]);head[b]=cnt++;
	  theEdge[cnt]=Edge (b,a,1,c,head[b]);head[b]=cnt++;
	  theEdge[cnt]=Edge (a,b,0,-c,head[a]);head[a]=cnt++;
	}
	theEdge[cnt]=Edge (0,1,2,0,head[0]);head[0]=cnt++;
	theEdge[cnt]=Edge (1,0,0,0,head[1]);head[1]=cnt++;
	theEdge[cnt]=Edge (n,n+1,2,0,head[n]);head[n]=cnt++;
	theEdge[cnt]=Edge (n+1,n,0,0,head[n+1]);head[n+1]=cnt++;
	s=0,t=n+1;
	mcost=0;
	while( mcmf() ) ;
	printf ("%d\n",mcost);
  }
  return 0;
}
bool mcmf ()
{
  for (int i=0;i<=n+1;i++) d[i]=INF;
  d[s]=0;
  memset (inqee,false,sizeof (inqee));
  p[s]=-1;a[s]=INF;inqee[s]=1;
  queue<int> qee;
  qee.push(s);
  while (!qee.empty ())
  {
	int x=qee.front ();qee.pop ();
	inqee[x]=0;//x可能已经在前面过程中,被当做e.to入队,需要置零
	for (int i=head[x];i!=-1;i=theEdge[i].next){
	  Edge e=theEdge[i];
	  if (e.cap>0 && d[e.to]>d[x]+e.cost){
		d[e.to]=d[x]+e.cost;
		a[e.to]=min_2 (a[x],e.cap);
		p[e.to]=i;
		if (!inqee[e.to]){//若该点已被访问,且存在更短的路,更小的费用,则需要更新a[],d[],p[],但是不能再将该点入队
		  qee.push (e.to);
		  inqee[e.to]=1;
		}
	  }
	}
  }
  if (d[t]==INF) return false;
  mcost+=d[t]*a[t];//a[t]=1或0
  for (int i=p[t]; i!=-1; i=p[theEdge[i].from]){
    theEdge[i].cap-=a[t];
	theEdge[i^1].cap+=a[t];
  }
  return true;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值