uva 10806 Dijkstra, Dijkstra.

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

两种理解

一种思路是最小费用最大流

另一种是最短路不唯一且两条或者多条最短路由共用边 ,或者最短路与次短路有共用边

我先写的是第二种思路,最小费用最大流还没写,明天补上,并且补上思路

 

第二种做法是,把无向图当做有向图处理,拆成两条边,先从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 <cstring>
#include <queue>
#define INF 0x3f3f3f3f
#define N 220
using namespace std;
int g[N][N],d[N],path[N],vis[N],n,m,s,t;
int sum;

void spfa()
{
    queue <int> q;
    for(int i=1; i<=n; i++)
    {d[i]=INF; vis[i]=0; path[i]=s;}
    d[s]=0; q.push(s); vis[s]=1;
    while(!q.empty())
    {
        int u;
        u=q.front(); q.pop(); vis[u]=0;
        for(int v=1; v<=n; v++)
            if(d[u]+g[u][v]<d[v])
            {
                d[v]=d[u]+g[u][v];
                path[v]=u;
                if(!vis[v])
                {  q.push(v); vis[v]=1;  }
            }
    }
    return ;
}
void change(int v)
{
    int u=path[v];
    g[u][v]=-g[u][v];  //来时候的路径权取反
    g[v][u]=INF;      //消除方向边
    if(u==s) return ;
    change(u);
}
int main()
{
    while(scanf("%d",&n)!=EOF && n)
    {
        scanf("%d",&m);
        memset(g,0x3f,sizeof(g));
        for(int i=1; i<=m; i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            g[u][v]=g[v][u]=w;
        }
        s=1; t=n;
        spfa();
        sum=d[t];
        if(d[t]==INF)  //第一次都不能去到终点,说明图不连通
        {
            printf("Back to jail\n");
            continue;
        }
        change(t);  //修改路径上的权和消除反向路径
        s=n; t=1;
        spfa();   //第二次运行最短路
        if(d[t]==INF)
            printf("Back to jail\n");
        else
            printf("%d\n",sum+d[t]);
    }
    return 0;
}

 

明天再补上最小费用最大流的思想和代码

最小费用最大流:由于题目规定了起点和终点分别为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的,(别忘了我们怎么设置的那两条特殊的有向边和他们的容量)。而最小费用最大流是用费用来求解最短路,所以我们把边权设置为费用,这样一求,就等价于原问题了

 

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
#define N 110
#define M 10010*4
#define INF 0x3f3f3f3f
struct edge
{int u,v,cap,cost,f,next;}e[M];
//一条有向边的信息有两个顶点,容量,费用,流量,指针
int d[N],first[N],p[M];
int C,F,n,m;

void add(int k,int u,int v,int cap,int cost,int f)
{
    e[k].u=u; e[k].v=v; 
    e[k].cap=cap;  e[k].cost=cost; e[k].f=f;
    e[k].next=first[u];
    first[u]=k;
}

void spfa(int s ,int t)
{//在还没有达到边的容量的条件下,以边权作为费用来寻找源点到汇点的最短路
    queue<int>q;
    int vis[N];
    memset(d,0x3f,sizeof(d));   d[s]=0;
    memset(vis,0,sizeof(vis));  vis[s]=1;
    memset(p,-1,sizeof(p));
    q.push(s);
    while(!q.empty())
    {
        int u,v,cap,cost,f;
        u=q.front(); q.pop(); vis[u]=0;
        for(int k=first[u]; k!=-1; k=e[k].next)
        {
            v=e[k].v; cap=e[k].cap; cost=e[k].cost; f=e[k].f;
            if(f<cap && d[u]+cost<d[v])
            {
                d[v]=d[u]+cost;
                p[v]=k;
                if(!vis[v])
                { q.push(v); vis[v]=1; }
            }
        }
    }
    return ;
}
void mincost_maxflow()
{
    int s,t;
    s=0; t=n+1;  //求从0到n+1的最小费用最大流
    C=F=0;
    while(1)
    {
        spfa(s,t);  //找最短路
        if(d[t]==INF)  //汇点不可达,说明已经达到了最小费用最大流
            break;
        int min=INF;  //保存最小残余流量
        for(int i=p[t]; i!=-1; i=p[e[i].u])  //沿路径返回
        {
            int cap=e[i].cap , f=e[i].f;
            min=min<cap-f ? min : cap-f;
        }

        for(int i=p[t]; i!=-1; i=p[e[i].u]) //增广
        {
            e[i].f+=min;
            e[i^1].f-=min;
        }

        F+=min;
        C+=d[t]*min;
    }

}
int main()
{
    while(scanf("%d",&n) && n)
    {
        scanf("%d",&m); m*=4;
        memset(first,-1,sizeof(first));
        for(int i=0; i<m; i+=4)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            //无向图最小费用最大流,1条边要当做4条处理
            add(i,u,v,1,w,0);
            add(i+1,v,u,0,-w,0);
            add(i+2,v,u,1,w,0);
            add(i+3,u,v,0,-w,0);
        }
        //一条有向边0-->1
        add(m,0,1,2,0,0);
        add(++m,1,0,0,0,0);
        //一条有向边n-->n+1
        add(++m,n,n+1,2,0,0);
        add(++m,n+1,n,0,0,0);

        mincost_maxflow();
        if(F==2) 
            printf("%d\n",C);
        else
            printf("Back to jail\n");
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值