【图论】最大流之EK算法与Dinic算法及最小费用最大流(转)

原博客:http://blog.csdn.net/hemk340200600/article/details/64131736
最大流:
给出一张网络图,并指定源点和终点,每条边都有它的容量,起点有着无限的流量,求从源点到经过的所有路径的最终到达汇点的最大流量和。对于同一个节点,流入的流量之和和流出的流量之和相同,即假如结点1有12流量流入结点2,结点2分别有8流量流入结点3,4流量流入结点4,这种情况是可以的。

EK算法:
而EK算法反复寻找源点s到汇点t之间的增广路径,若有,找出增广路径上每一段[容量-流量]的最小值delta,若无,则结束。 并且更新残留网络的值(涉及到反向边)。所有的正向边减去delta,所有的反向边加上delta.由于反向边存在容量,所以下次寻找增广路径可以走该反向边,这种设计使得我们可以抵消之前的操作,找到更为适合的使总流量增加的边,使程序有了一个后悔和改正的机会。找到delta后,则使最大流值加上delta,更新为当前的最大流值。

int maxData = 0x7fffffff;  
int capacity[arraysize][arraysize]; //记录残留网络的容量  
int flow[arraysize];                //标记从源点到当前节点实际还剩多少流量可用  
int pre[arraysize];                 //标记在这条路径上当前节点的前驱,同时标记该节点是否在队列中  
int n,m;  
queue<int> myqueue;  
int BFS(int src,int des)  
{  
    int i,j;  
    while(!myqueue.empty())       //队列清空  
        myqueue.pop();  
    for(i=1;i<m+1;++i)pre[i]=-1;  
    pre[src]=0;  
    flow[src]= maxData;  
    myqueue.push(src);  
    while(!myqueue.empty())  
    {  
        int index = myqueue.front();  
        myqueue.pop();  
        if(index == des)break;//找到了增广路径,break掉   
        for(i=1;i<m+1;++i)  
        {  
            if(i!=src && capacity[index][i]>0 && pre[i]==-1)  
            {  
                 pre[i] = index; //记录前驱  
                 flow[i] = min(capacity[index][i],flow[index]);   //关键:迭代的找到增量  
                 myqueue.push(i);  
            }  
        }  
    }  
    if(pre[des]==-1)      //残留图中不再存在增广路径  
        return -1;  
    else  
        return flow[des];  
}  
int maxFlow(int src,int des)  
{  
    int increasement= 0;  
    int sumflow = 0;  
    while((increasement=BFS(src,des))!=-1)  
    {  
         int k = des;          //利用前驱寻找路径  
         while(k!=src)  
         {  
              int last = pre[k];  
              capacity[last][k] -= increasement; //改变正向边的容量  
              capacity[k][last] += increasement; //改变反向边的容量  
              k = last;  
         }  
         sumflow += increasement;  
    }  
    return sumflow;  
}  
int main()  
{  
    int i,j;  
    int start,end,ci;  
    while(cin>>n>>m)  
    {  
        memset(capacity,0,sizeof(capacity));  
        memset(flow,0,sizeof(flow));  
        for(i=0;i<n;++i)  
        {  
            cin>>start>>end>>ci;  
            if(start == end)               //考虑起点终点相同的情况  
               continue;  
            capacity[start][end] +=ci;     //此处注意可能出现多条同一起点终点的情况  
        }  
        cout<<maxFlow(1,m)<<endl;  
    }  
    return 0;  
}  

Dinic算法:
最核心的内容就是多路增广。利用对整张图的分层,即源点为第一层,与源点相连的并且有容量的点为第二层,与第二层相连并且有容量的点为第三层……如果不能到达终点,说明找不到增广路径了,此时也就达到了最大流。一次BFS可以增广好几次。效率比起EK算法大大提高。

Dinic算法最核心的内容就是多路增广。利用对整张图的分层,一次BFS可以增广好几次。效率比起EK算法大大提高。   
*/   
int tab[250][250];//邻接矩阵   
int dis[250];//距源点距离,分层图   
int q[2000],h,r;//BFS队列 ,首,尾   
int N,M,ANS;//N:点数;M,边数   
int BFS()  
{  
     int i,cur;  
     memset(dis,-1,sizeof(dis));//以-1填充   
     dis[1]=0;  
     head=0;tail=1;  
     que[0]=1;//源点入队   
     while (head<tail)  
     {  
           cur=que[head++];  
           for (i=1;i<=N;i++)  
               if (dis[i]<0 && tab[cur][i]>0)  
               {  
                  dis[i]=dis[cur]+1;   
                  que[tail++]=i;  
               }  
     }  
     if (dis[N]>0)  
        return 1;  
     else  
        return 0;//汇点的DIS小于零,表明BFS不到汇点   
}  
//Find代表一次增广,函数返回本次增广的流量,返回0表示无法增广   
int find(int x,int low)//Low是源点到现在最窄的(剩余流量最小)的边的剩余流量  
{  
    int i,a=0;  
    if (x==N)return low;//是汇点   
    for (i=1;i<=N;i++)  
    if (tab[x][i] >0 //联通   
     && dis[i]==dis[x]+1 //是分层图的下一层   
     &&(a=find(i,min(low,tab[x][i]))))//能到汇点(a <> 0)   
    {  
       tab[x][i]-=a;  
       tab[i][x]+=a;  
       return a;  
    }  
    return 0;  

}  
int main()  
{  
    //freopen("ditch.in" ,"r",stdin );  
    //freopen("ditch.out","w",stdout);  
    int i,j,f,t,flow,tans;  
    while (scanf("%d%d",&M,&N)!=EOF){  
    memset(tab,0,sizeof(tab));  
    for (i=1;i<=M;i++)  
    {  
        scanf("%d%d%d",&f,&t,&flow);  
        tab[f][t]+=flow;  
    }  
    //  
    ANS=0;  
    while (BFS())//要不停地建立分层图,如果BFS不到汇点才结束   
    {  
          while(tans=find(1,0x7fffffff))ANS+=tans;//一次BFS要不停地找增广路,直到找不到为止   
    }  
    printf("%d\n",ANS);  
    }  
    system("pause");  
}  

最小费用最大流:
在最大流有多组解时,给每条边在附上一个单位费用的量,问在满足最大流时的最小费用是多少?
最小费用最大流只是在残留网络的基础上多了个费用网络。在最大流的基础上,将费用看成路径长度,求最短路即可。 注意一开始反向边的费用为正向边的负数。
例如POJ2516这道题,有N个供给商,M个雇主,K种物品。每个供给商对每种物品的的供给量已知,每个雇主对每种物品的需求量的已知,从不同的供给商输送不同的货物到不同的雇主手上需要不同的花费,又已知从供给商Mj送第kind种货物的单位数量到雇主Ni手上所需的单位花费。问:供给是否满足需求?若是满足,最小运费是多少?
代码很好理解,可当做模板。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
#include<stack>
#define eps 1e-8
#define ll long long
const int inf = 0x3f3f3f3f;
const long long mod=1e9+7;
const int nMax=105;
using namespace std;
int mapp[nMax][nMax];//mapp[i][j]表示对于每种k物品从i运输到j所花费的钱
int vis[nMax];//表示i是否用过
int cap[nMax][nMax];//表示i到j的最大通货量
int dis[nMax];//到i的距离
queue<int>q;
int pre[nMax];//保存每一条最短增流路
int num,ans;//num最后的汇点,ans最终的答案
void end()
{
    int i, sum = inf;
    for (i = num; i!= 0; i = pre[i])//找到可以增加的最大的流,是整条最短路上的最小流
        sum = min(sum, cap[pre[i]][i]);
    for (i = num; i != 0; i = pre[i])
    {
        cap[pre[i]][i] -= sum;//正向减去增加的流
        cap[i][pre[i]] += sum;//逆向加上增加的流
    }
    ans+=sum*dis[num];//计算本次的花费,实际上就是从place pre[i]到第i个人对于当前种类的物品所花费的钱
}
int spfa()//spfa求最短路径,dijstra不允许有负权,所以这里使用spfa
{
    int i,k;
    memset(vis, 0, sizeof(vis));
    for (i = 0; i <= num; ++ i)
    {
    dis[i] = inf;
    }
    dis[0] = 0;
    vis[0] = 1;
    q.push(0);
    while (!q.empty())
    {
        k=q.front();
        q.pop();
        vis[k] = 0;
        for (i = 0; i <= num; ++ i)
        {
            if (cap[k][i] && dis[i] > dis[k] + mapp[k][i])//如果k到i还有量,表明还可以增流,那么就求最短路
            {
                dis[i] = dis[k] + mapp[k][i];
                pre[i] = k;
                if (!vis[i])
                {
                    vis[i] = 1;
                    q.push(i);
                }
            }
        }
    }
    if (dis[num]==inf)   return 0;
      return 1;
}
    int main()
{
    int N,M,K,i,j,k;
    int need[nMax][nMax];
    int needk[nMax];
    int have[nMax][nMax];
    int havek[nMax];
    int flag;
    while (scanf("%d %d %d", &N, &M, &K), N)
    {
    memset(needk, 0, sizeof(needk));
    for (i = 1; i <= N; ++ i)
    {
    for (j = 1; j <= K; ++ j)
    {
    scanf("%d", &need[i][j]);
    needk[j] += need[i][j];//求出每种货物最大的需求量
    }
    }
    memset(havek, 0, sizeof(havek));
    for (i = 1; i <= M; ++ i)
    {
    for (j = 1; j <= K; ++ j)
    {
    scanf("%d", &have[i][j]);
    havek[j] += have[i][j];//计算出所有地方能提供出每种货物的最大量
    }
    }
    flag = 1;
    for (i = 1; i <= K; ++ i)
    {
    if (needk[i] > havek[i])//如果有物品供给不足,那么肯定不能完成运送
    {
    flag = 0;
    break;
    }
    }
    ans = 0;
    num = N + M + 1;
    for (k = 1; k <= K; ++ k)//计算每种货物的最小花费,然后求和
    {
        memset(cap, 0, sizeof(cap));
        memset(mapp, 0, sizeof(mapp));
        for (i = 1; i <= N; ++ i)
        {
        for (j = 1; j <= M; ++ j)
        {
            scanf("%d", &mapp[j][M + i]);//将N个人映射到M+1-M+N区间上,这样方便建图,map j到M+i就是从地方j运送到人i的花费
            mapp[M + i][j] = -mapp[j][M + i];
            cap[j][M + i] = have[j][k];//j到i的量是第k种货物的在place j的最大的量
        }
        }
        if (!flag)
        {
            continue;
        }
        for (i = 1; i <= M; ++ i)
        {
            cap[0][i] = have[i][k];//源点到place i其实也设为第k中货物在place i的量
        }
        for (i = 1; i <= N; ++ i)
        {
            cap[M + i][num] = need[i][k];//从人i到汇点的量设为第i个人对第k种货物的需求量。
        }
        while(spfa())
            end();
    }
    if (flag)
    {
    printf("%d\n", ans);
    }
    else
    printf("-1\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值