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;

}

 

你可能感兴趣的:(dijkstra)