poj 2135 最小费用最大流(从1到n往返不走重复路的最小距离)

题意:给定n个谷仓,有m条双向路径,每条连接两个谷仓。一个人要从1号谷仓走到n号谷仓,再从n号谷仓走回1号(来回走的路不能重复)。给定路径长度,问来回走的最短长度。

思路:由于来回可以看成2条从1到n的不同的路,所以转化为求从1到n的两条不同的路。按照最小费用流建图:假设a b之间有长度为c的路,那么ab之间费用为c,容量是1;ba之间费用为c,容量是1。(同时要建立b到a的容量为0,费用为-c的路径;也就是输入给出一条边,需要添加到邻接表中四条边)接着建立一个超级源点,连接1号景点,无费用,容量为2;同理建立一个超级汇点,连接n号景点,无费用,容量为2。

针对这道题,更新路径值的时候实际上可以断定流量只能增加1(因为每条边的流量最多为1),而且题目保证有解,进而可知spfa必执行两次,所以更新函数可以简写(见版本2)

#include <stdio.h>
#include <string.h>
#define N 1005
#define M 10005
#define min(a,b) a<b?a:b
#define INF 0x3fffffff
struct edge{
	int x,y,cost,cap,next;
}e[M*4];
int n,m,top,res;
int dis[N],visited[N],pre[N],q[200000],index[N],first[N];
void add(int x,int y,int c,int w){
	e[top].y = y;
	e[top].cap = c;
	e[top].cost = w;
	e[top].next = first[x];
	first[x] = top++;
	e[top].y = x;
	e[top].cap = 0;
	e[top].cost = -w;
	e[top].next = first[y];
	first[y] = top++;
}
int spfa(){//费用当做权值,求最短路
	int i,front,rear,now;	
	for(i = 0;i<=n;i++)
		dis[i] = INF;
	memset(visited,0,sizeof(visited));
	memset(pre,-1,sizeof(pre));
	front = rear = -1;
	q[++rear] = 0;
	dis[0] = 0;
	while(front < rear){
		now = q[++front];
		visited[now] = 0;
		for(i = first[now];i!=-1;i=e[i].next)
			if(e[i].cap && dis[e[i].y]>dis[now]+e[i].cost){
				dis[e[i].y] = dis[now]+e[i].cost;
				if(!visited[e[i].y]){
					visited[e[i].y] = 1;
					q[++rear] = e[i].y;
				}
				index[e[i].y] = i;//保存松弛的节点在邻接表中的位置
				pre[e[i].y] = now;
			}
	}
	if(dis[n]!=INF)
		return 1;
	return 0;
}
void sum(){
	int i,aug=INF;
	for(i = n;i!=0;i=pre[i])//得到此次增加的容量
		aug = min(aug,e[index[i]].cap);
	res += dis[n]*aug;
	for(i = n;i!=0;i=pre[i]){
		e[index[i]].cap -= aug;
		e[index[i]^1].cap += aug;
	}
}
int main(){
	freopen("a.txt","r",stdin);
	while(scanf("%d %d",&n,&m)!=EOF){
		int i,a,b,c;
		top = res = 0;
		memset(first,-1,sizeof(first));
		for(i = 0;i<m;i++){
			scanf("%d %d %d",&a,&b,&c);
			add(a,b,1,c);
			add(b,a,1,c);
		}
		add(0,1,2,0);//添加超级源点
		add(n,n+1,2,0);//添加超级汇点
		n++;
		while(spfa())//每求一次最短路,更新一下图的容量
			sum();
		printf("%d\n",res);
	}
	return 0;
}

版本2:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <cstdlib>
using namespace std;
#define INF 0x3fffffff
#define N 1005
#define M 40005
struct edge{
    int y,next,w,c;
}e[M];
int n,m,first[N],top,dis[N],used[N],pre[N],id[N],res;
void add(int x,int y,int w,int c){
    e[top].y = y;
    e[top].w = w;
    e[top].c = c;
    e[top].next = first[x];
    first[x] = top++;
}
int spfa(){
    int i,now;
    for(i = 1;i<=n+1;i++)
        dis[i] = INF;
    dis[0] = 0;
    queue<int> q;
    q.push(0);
    memset(used, 0, sizeof(used));
    used[0] = 1;
    while(!q.empty()){
        now = q.front();
        q.pop();
        used[now] = 0;
        for(i = first[now];i!=-1;i=e[i].next){
            if(e[i].c>0 && dis[e[i].y] > dis[now]+e[i].w){
                dis[e[i].y] = dis[now]+e[i].w;
                if(!used[e[i].y]){
                    used[e[i].y] = 1;
                    q.push(e[i].y);
                }
                id[e[i].y] = i;
                pre[e[i].y] = now;
            }
        }
    }
    return dis[n+1]<INF;
}
void update(){
    int i;
    res+=dis[n+1];
    for(i = n+1;i!=0;i=pre[i]){
        e[id[i]].c --;
        e[id[i]^1].c ++;
    }
}
int main(){
    while(scanf("%d %d",&n,&m)!=EOF){
        int i,a,b,w;
        res = top = 0;
        memset(first, -1, sizeof(first));
        for(i = 1;i<=m;i++){
            scanf("%d %d %d",&a,&b,&w);
            add(a,b,w,1);
            add(b,a,-w,0);
            add(b,a,w,1);
            add(a,b,-w,0);
        }
        add(0,1,0,2);
        add(1,0,0,0);
        add(n,n+1,0,2);
        add(n+1,n,0,0);
        while(spfa())
            update();
        printf("%d\n",res);
    }
    return 0;
}


你可能感兴趣的:(poj 2135 最小费用最大流(从1到n往返不走重复路的最小距离))