• 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;
    }
  • 相关阅读:
    513. Find Bottom Left Tree Value(LeetCode)
    647. Palindromic Substrings(LeetCode)
    537. Complex Number Multiplication(LeetCode)
    338. Counting Bits(LeetCode)
    190. Reverse Bits(leetcode)
    Java多线程核心技术
    正则表达式
    linux 怎么把^M去掉
    分片与分区的区别
    《MYSQL技术精粹》读书笔记
  • 原文地址:https://www.cnblogs.com/scau20110726/p/2810654.html
Copyright © 2020-2023  润新知