UVa 10806 Dijkstra, Dijkstra (无向图+最小费用最大流+最短路:)

参考:http://www.cnblogs.com/scau20110726/archive/2012/12/09/2810654.html

题意:

     固定起点1和终点n,从1到n,再从n回到1,去和回的路上相同的边只能用一次,求两次的和最短,如果去的时候不能去到终点或者回的时候回不到起点那么就输出Back to jail,否则输出两次和的最小值(此图是无向图,不会有重边,边的权值在大于1小于1000)

最小费用最大流:

     由于题目规定了起点和终点分别为1和n,所以我们另外设置一个源点和汇点0和n+1,0到1有一条有向边,n到n+1有一条有向边,这两条边的容量都是2,单位费用都是0

另外,题目给的边(无向边),全部拆成两条有向边,容量都是1,单位费用就是原本给的边权。然后求新源点0到新汇点n+1的最小费用最大流,如果最后最大流量为2,那么原问题成功,输出最小费用,最小费用即原问题的解。如果最大流量小于2,那么原问题失败,输出Back to jail

这个算法的正确性是什么呢?其实,原图的边,容量都设置为1,就起到了“每条边只走一次的要求”,因为每次增广后,某些边一定会满流,不会再经过,而从0出发到n+1,如果最后流量为2,那说明其实有两条路径能到达n+1,而从0到n+1,是必须经过1和n的,(别忘了我们怎么设置的那两条特殊的有向边和他们的容量)。而最小费用最大流是用费用来求解最短路,所以我们把边权设置为费用,这样一求,就等价于原问题了。

<span style="font-weight: normal;">#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<climits>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<queue>
#include<vector>
using namespace std;

const int INF=1000000000;
const int maxn=100+10;
int n,m,s,t;

struct Edge
{
	int from,to,cap,flow,cost;
	Edge(int from=0,int to=0,int cap=0,int flow=0,int cost=0):from(from),to(to),cap(cap),flow(flow),cost(cost){}
};

vector<Edge> edges;
vector<int> G[maxn];
int inq[maxn];
int d[maxn];
int p[maxn];
int a[maxn];

void init()
{
	for(int i=0;i<=n+1;i++)G[i].clear();
	edges.clear();
}

void AddEdge(int from,int to,int cap,int cost)
{
	edges.push_back(Edge(from,to,cap,0,cost));
	edges.push_back(Edge(to,from,0,0,-cost));
	int v=edges.size();
	G[from].push_back(v-2);
	G[to].push_back(v-1);
}

bool BellmanFord(int s,int t,int &flow,int &cost)
{
	for(int i=0;i<=n+1;i++)d[i]=INF;
	memset(inq,0,sizeof(inq));
	d[s]=0;
	inq[s]=1;
	p[s]=0;
	a[s]=INF;

	queue<int> q;
	q.push(s);
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		inq[u]=0;
		for(int i=0;i<G[u].size();i++)
		{
			Edge& e=edges[G[u][i]];
			if(e.cap>e.flow&&d[e.to]>d[u]+e.cost)
			{
				d[e.to]=d[u]+e.cost;
				p[e.to]=G[u][i];
				a[e.to]=min(a[u],e.cap-e.flow);
				if(!inq[e.to]){q.push(e.to);inq[e.to]=1;}
			}
		}
	}
	if(d[t]==INF)return false;
	flow+=a[t];
	cost+=d[t]*a[t];
	int u=t;
	while(u!=s)
	{
		edges[p[u]].flow+=a[t];
		edges[p[u]^1].flow-=a[t];
		u=edges[p[u]].from;
	}
	return true;
}

void solve(int s,int t)
{
	int flow=0,cost=0;
	while(BellmanFord(s,t,flow,cost));
	if(flow<2)printf("Back to jail\n");
	else printf("%d\n",cost);
}

int main()
{
	while(~scanf("%d",&n))
	{
		if(n==0)break;
		scanf("%d",&m);
		s=0;
		t=n+1;
		init();
		for(int i=0;i<m;i++)
		{
			int u,v,c;
			scanf("%d%d%d",&u,&v,&c);
			AddEdge(u,v,1,c);
			AddEdge(v,u,1,c);
		}
		AddEdge(0,1,2,0);
		AddEdge(n,n+1,2,0);
		solve(s,t);
	}
	return 0;
}</span>

最短路:

      把无向图当做有向图处理,拆成两条边,先从1到n做一次最短路,并且要记录这条有向路径。最短路结束后,记录下最短路的值d[t],如果为d[t]=INF(t=n)说明图都不连通,从起点娶不到终点。

如果连通能去到,那么把这条路径上的边的值都取相反数,并且删除它的相反边,即赋值为INF

即  u--->v  是去的时候路径的其中有向边,那么g[u][v]=-g[u][v];  g[v][u]=INF;

然后从n到1再运行一遍最短路,因为这次有些边是负值,所以dij不能用,可以用spfa,所以我的代码两次都是spfa,写一个就行了,有些人是先写一个dij再写一个spfa也可以的

如果这次最短路d[t]=INF(此时的t=1),说明回不去了,那么失败

 

很多人见到题目,就是直接1到n一次最短路,n到1一次最短路,再求和,但是这样是错的,我也是这样做,WA

那么这个算法的正确性是什么呢

如果从1到n有两(多)条最短路,并且有些最短路没有共用边,即完全分离的,那么去的时候用 一条,回的时候用一条,互不干扰,最后的和就是最短路*2

同样的,如果只有1条最短路和次短路(1条和多条都无所谓),而且他们没有共用边,那么去的时候用最短路,回的时候用次短路,互不干扰。最后的和是最短路+次短路

但是,不幸的是,可能有两次最短路,但是却有共用边,那么去的时候肯定会用掉这条共用边,因为回来的时候不能再用这条共用边,那么是不是应该完全放弃另一条没有用过的最短路而另寻路径呢?不是的,而是可以用一个最好的方法,就是消去那条共用边的权(想想为什么来时候的边权要取反)

可以这样理解,两条最短路,都可以看成 前部分+共用边+后部分 , 这里前部分和后部分都是独立的,没有共用的,那么综合两次的走法,其实可以变为 最短路1的前部分+最短路2的后部分,为去的路径,最短路2的后部分+最短路1的前部分,为回的路径,这样子,相当于交换了路径,但是我们并不关心路径,我们只关心两次和最小,这样并不改变和,而且还消掉了共用边的权,其实相当于两次走都没有进过共用边

所以这样的和为 最短路*2-所有共用边的权

 

同样,所过最短路和次短路有共用边,那么同样是相当于交叉了两次的路径,但是并不改变权和,而且消去了共用边的权

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iostream>
#include<queue>
#include<vector>
#include<set>
#include<climits>
#include<map>
#include<string>
using namespace std;

const int INF=1000000000;
const int maxn=100+10;
int n,m;
int d[maxn];
int G[maxn][maxn];
int p[maxn];

void change(int num)
{
    if(num==1)return ;
    int u=p[num];
    G[u][num]=-G[u][num];
    G[num][u]=INF;
    change(u);
}

void Bellman_Ford(int s)
{
    queue<int> q;
    bool inq[maxn];
    for(int i=1;i<=n;i++)
       {
            d[i]=(i==s ? 0:INF);
            p[i]=1;
       } 
    memset(inq,0,sizeof(inq));
    q.push(s);
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        inq[x]=false;
        for(int i=1;i<=n;i++)if(d[i]>d[x]+G[x][i])
        {
            d[i]=d[x]+G[x][i];
            p[i]=x;
            if(!inq[i])
            {
                inq[i]=true;
                q.push(i);
            }
        }
    }
}

int main()
{
    while(~scanf("%d",&n))
    {
        if(n==0)break;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            {
                if(i==j)G[i][j]=0;
                else G[i][j]=INF;
            }
        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            int u,v,c;
            scanf("%d%d%d",&u,&v,&c);
            G[u][v]=c;
            G[v][u]=c;
        }
        Bellman_Ford(1);
        int res=0;
        if(d[n]==INF)printf("Back to jail\n");
        else
        {
            res+=d[n];
            change(n);
            Bellman_Ford(n);
            if(d[1]==INF)printf("Back to jail\n");
            else {res+=d[1];printf("%d\n",res);}
        }
    }
    return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值