【费用流】hdu1533 poj2516 bzoj1070 bzoj1061

费用流是在网络流的基础上求流最大的前提下使得费用最小(或者最大)。

算法一:SPFA寻找增广路
在isap算法中,是当 dis[v]+1==dis[u] 时才访问 v 。即边(u,v)的边权为 1 。在这里令边权为流过该边的费用即可。

bool spfa()
{
    queue<int>q;
    for(int i=b;i<=e;++i)dis[i]=inf, pre[i]=i;
    q.push(b), dis[b]=0;
    int u, v;
    while(!q.empty())
    {
        u=q.front();
        q.pop();
        in[u]=0;
        for(node *p=adj[u];p!=NULL;p=p->next)
            if(p->cap>0&&dis[(v=p->v)]>dis[u]+p->w)
            {
                dis[v]=dis[u]+p->w;
                pre[v]=u, temp[v]=p;
                if(!in[v])q.push(v), in[v]=1;
            }
    }
    return dis[e]!=inf;
}

int solve()
{
    int ans=0, delta;
    while(spfa())
    {
        delta=inf;
        for(int i=e;i!=b;i=pre[i])
            delta=min(delta,temp[i]->cap);
        for(int i=e;i!=b;i=pre[i])
        {
            temp[i]->cap-=delta, temp[i]->back->cap+=delta;
            temp[i]->back->w=-temp[i]->w;
            ans+=delta*temp[i]->w;
        }
    }
    return ans;
}

算法二:zkw费用流
当寻找到增广路使得源点与汇点分开时(即不存在dis[v]+w(u,v)==dis[u]),在以源点为集合中找到一个最小差值。将源点集合中的节点的 dis 全部加上该差值。

bool label()
{
    int mv=inf;
    for(int i=b;i<=e;++i)
        if(vis[i])
            for(node *p=adj[i];p!=NULL;p=p->next)
                if(!vis[p->v]&&p->cap>0)
                    mv=min(mv,p->w+vis[p->v]-vis[i]);
    if(mv==inf)return 0;
    for(int i=b;i<=e;++i)
        if(vis[i])dis[i]+=mv;
    return 1;
}

int aug(int u,int augco)
{
    if(u==e)
    {
        ans+=dis[b]*augco;
        return augco;
    }
    vis[u]=1;
    int augc=augco, delta, v;
    for(node *p=adj[u];p!=NULL;p=p->next)
    {
        v=p->v;
        if(!vis[v]&&p->cap>0&&dis[v]+p->w==dis[u])
        {
            delta=aug(v,min(augc,p->cap));
            p->cap-=delta, p->back->cap+=delta, augc-=delta;
        }
    }
    return augco-augc;
}

hdu1533
题目大意:在 nm 的方格上有若干个房子和人,每个房子只能住一个人。人只能向上下左右移动。求使得所有人都有房子住的最小距离和。

这是一个很明显的二分图,可以用KM算法完成最有匹配。
当然可以用费用流。由于是一个二分图,增广路不会很长,用zkw费用流更快。

#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 105
#define MAXM 100005
#define abs(a) ((a)>0?(a):(-(a)))
#define min(a,b) ((a)<(b)?(a):(b))
#define inf 0x3f3f3f3f
using namespace std;

char Map[MAXN][MAXN];
int n, m, ans, dis[MAXN<<1], b, e;
int human[MAXN][2], house[MAXN][2], cnt, cnth;
bool vis[MAXN<<1];

struct node
{
    int v, w, cap;
    node *front, *next;
    inline void init(){v=w=cap=0, front=next=0;}
}edge[MAXM<<1], *adj[MAXN<<1], *pos=edge, *temp[MAXN<<1];

inline void add(int a,int b,int c,int d)
{
    pos->v=b, pos->w=c, pos->cap=d, pos->next=adj[a], pos->front=pos+1;
    adj[a]=pos;
    ++pos;
    pos->v=a, pos->w=-c, pos->cap=0, pos->next=adj[b], pos->front=pos-1;
    adj[b]=pos;
    ++pos;
}

int cal(int a1,int b1,int a2,int b2){return abs(a1-a2)+abs(b1-b2);}

void build()
{
    memset(adj,0,sizeof adj);
    memset(dis,0,sizeof dis);
    for(node *p=edge;p!=pos;++p)p->init();
    pos=edge;
    for(int i=1;i<=cnt;++i)
        for(int j=1;j<=cnth;++j)
            add(i,j+cnt,cal(human[i][0],human[i][1],house[j][0],house[j][1]),1);
    for(int i=1;i<=cnt;++i)add(0,i,0,1);
    n=cnt+cnth+1;
    for(int i=cnt+1;i<=cnth+cnt;++i)add(i,n,0,1);
    b=0, e=n;
}

bool lable()
{
    int mv=inf;
    for(int i=b;i<=e;++i)
        if(vis[i])
            for(node *p=adj[i];p!=NULL;p=p->next)
                if(!vis[p->v]&&p->cap>0)
                    mv=min(mv,p->w+dis[p->v]-dis[i]);
    if(mv==inf)return 0;
    for(int i=b;i<=e;++i)
        if(vis[i])dis[i]+=mv;
    return 1;
}

int aug(int u,int augco)
{
    if(u==e)
    {
        ans+=dis[b]*augco;
        return augco;
    }
    int augc=augco, delta, v;
    vis[u]=1;
    for(node *p=adj[u];p!=NULL&&augc;p=p->next)
        if(!vis[(v=p->v)]&&p->cap>0&&dis[u]==dis[v]+p->w)
        {
            delta=aug(v,min(p->cap,augc));
            p->cap-=delta, p->front->cap+=delta;
            augc-=delta;
        }
    return augco-augc;
}

int main()
{
    while(~scanf("%d%d",&n,&m)&&n+m)
    {
        ans=cnt=cnth=0;
        for(int i=1;i<=n;++i)
            scanf("%s",Map[i]+1);
        for(int i=1;i<=n;++i)
            for(int j=1;j<=m;++j)
                if(Map[i][j]=='m')
                    human[++cnt][0]=i, human[cnt][1]=j;
                else if(Map[i][j]=='H')
                    house[++cnth][0]=i, house[cnth][1]=j;
        build();
        do
        {
            do{memset(vis,0,sizeof vis);}
            while(aug(b,inf));
        }while(lable());
        printf("%d\n",ans);
    }
    return 0;
}

poj2516
题目大意:有 n 个店铺要进k种货,有 m 个厂库。已知每个店铺对每种货的需求量,每个厂库每种货的库存以及每个厂库到每个店铺的单位费用。求最小费用。

如果只有一种货物,就是一个二分图的最优匹配。
所有对这k种货物分别处理,将费用加起来即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#define inf 2147483647
#define MAXN 55
#define MAXM 5005
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;

int n, m, k, b, e, cnt;
int d[MAXN][MAXN], need[MAXN][MAXN], have[MAXN][MAXN];
int dis[MAXN<<1], ans;

struct node
{
    int v, w, cap;
    node *front, *next;
    inline void init()
    {
        v=w=cap;
        front=next=0;
    }
}edge[MAXM<<1], *adj[MAXN<<1], *pos=edge;

inline void add(int a,int b,int c,int d)
{
    pos->v=b, pos->w=c, pos->cap=d, pos->front=pos+1, pos->next=adj[a];
    adj[a]=pos;
    ++pos;

    pos->v=a, pos->w=-c, pos->cap=0, pos->front=pos-1, pos->next=adj[b];
    adj[b]=pos;
    ++pos;
}

bool vis[MAXN<<1];

void build()
{
    memset(dis,0,sizeof dis);
    b=0, e=n+m+1;
    for(node *p=edge;p!=pos;++p)p->init();
    pos=edge;
    memset(adj,0,sizeof adj);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            add(j,i+m,d[i][j],inf);
    for(int i=1;i<=m;++i)add(0,i,0,have[i][cnt]);
    for(int i=1;i<=n;++i)add(i+m,e,0,need[i][cnt]);
}

bool label()
{
    int mv=inf;
    for(int i=b;i<=e;++i)
        if(vis[i])
            for(node *p=adj[i];p!=NULL;p=p->next)
                if(!vis[p->v]&&p->cap>0)
                    mv=min(mv,p->w+dis[p->v]-dis[i]);
    if(mv==inf)return 0;
    for(int i=b;i<=e;++i)
        if(vis[i])dis[i]+=mv;
    return 1;
}

int aug(int u,int augco)
{
    if(u==e)
    {
        ans+=dis[b]*augco;
        return augco;
    }
    vis[u]=1;
    int augc=augco, delta, v;
    for(node *p=adj[u];p!=NULL;p=p->next)
    {
        v=p->v;
        if(!vis[v]&&p->cap>0&&dis[v]+p->w==dis[u])
        {
            delta=aug(v,min(p->cap,augc));
            p->cap-=delta, augc-=delta;
            p->front->cap+=delta;
        }
    }
    return augco-augc;
}

bool check()
{
    int sum;
    for(int i=1;i<=k;++i)
    {
        sum=0;
        for(int j=1;j<=n;++j)
            sum-=need[j][i];
        for(int j=1;j<=m&&sum<0;++j)
            sum+=have[j][i];
        if(sum<0)return 1;
    }
    return 0;
}

int main()
{
    while(~scanf("%d%d%d",&n,&m,&k)&&n+m+k)
    {
        ans=0;
        for(int i=1;i<=n;++i)
            for(int j=1;j<=k;++j)
                scanf("%d",&need[i][j]);
        for(int i=1;i<=m;++i)
            for(int j=1;j<=k;++j)
                scanf("%d",&have[i][j]);
        if(check())
        {
            for(cnt=1;cnt<=k;++cnt)
                for(int i=1;i<=n;++i)
                    for(int j=1;j<=m;++j)
                        scanf("%d",&d[i][j]);
            puts("-1");
            continue;
        }
        for(cnt=1;cnt<=k;++cnt)
        {
            for(int i=1;i<=n;++i)
                for(int j=1;j<=m;++j)
                    scanf("%d",&d[i][j]);
            build();
            do
            {
                do{memset(vis,0,sizeof vis);}while(aug(b,inf));
            }while(label());
        }
        printf("%d\n",ans);
    }
    return 0;
}

bzoj1070[scoi2007]
省选难度的建模果真不一样…
想到贪心的请注意一下数据范围,这么小贪心的可能性不太大…
不知道非要来除以一个总数有何意义(可能是为了迷惑那些想用分数规划的人吧)

言归正传,如何建模是这道题的难点。
考虑自己对别人的影响。有一点点逆向思维的感觉。
若第 i 个车被第j个技术人员倒数第 k 个修,那么对答案的贡献就是kcost[i][j]。它会使后面 k1 个车多等 cost[i][j] ,同时它自己耗时 cost[i][j]
所以说就以这种状态建立节点。

蒟蒻一开始就想到了费用流,感觉应该要拆点,又不知道怎么拆。之后想到了将每个技术人员拆成 m m为顾客数)个点,然后就不知道怎样定义边权……
特别注意区分 n,m 的意义,要不然调半天都不知道哪里错了…

#include <iostream>
#include <cstdio>
#include <cstring>
#define inf 2147483647
#define MAXN 605
#define MAXM 100005
using namespace std;

int n, m, b, e, ans;
int dis[MAXN], cost[61][10];
bool vis[MAXN];

struct node
{
    int v, w, cap;
    node *front, *next;
    inline void init()
    {v=w=cap=0, front=next=0;}
}edge[MAXM<<1], *adj[MAXN], *pos=edge;

inline void add(int a,int b,int c,int d)
{
    pos->v=b, pos->w=c, pos->cap=d, pos->front=pos+1, pos->next=adj[a];
    adj[a]=pos;
    ++pos;

    pos->v=a, pos->w=-c, pos->cap=0, pos->front=pos-1, pos->next=adj[b];
    adj[b]=pos;
    ++pos;
}

bool label()
{
    int mv=inf;
    for(int i=b;i<=e;++i)
        if(vis[i])
            for(node *p=adj[i];p!=NULL;p=p->next)
                if(!vis[p->v]&&p->cap>0)
                    mv=min(mv,dis[p->v]+p->w-dis[i]);
    if(mv==inf)return 0;
    for(int i=b;i<=e;++i)
        if(vis[i])
            dis[i]+=mv;
    return 1;
}

int aug(int u,int augco)
{
    if(u==e)
    {
        ans+=dis[b]*augco;
        return augco;
    }
    vis[u]=1;
    int augc=augco, delta, v;
    for(node *p=adj[u];p!=NULL;p=p->next)
    {
        v=p->v;
        if(!vis[v]&&p->cap>0&&dis[v]+p->w==dis[u])
        {
            delta=aug(v,min(p->cap,augc));
            p->cap-=delta, p->front->cap+=delta;
            augc-=delta;
        }
    }
    return augco-augc;
}

void build()
{
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            for(int k=1;k<=m;++k)
                add(k,m*i+j,cost[k][i]*j,1);
    b=0, e=n*m+m+1;
    for(int i=m+1;i<e;++i)add(i,e,0,1);
    for(int i=1;i<=m;++i)add(0,i,0,1);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i)
        for(int j=1;j<=n;++j)
            scanf("%d",&cost[i][j]);
    build();
    do
    {
        do{memset(vis,0,sizeof vis);}while(aug(b,inf));
    }while(label());
    printf("%.2lf\n",ans/double(m));
    return 0;
}

bzoj1061[noi2008]
建模神题…看到题根本就木有思路啊( ⊙ o ⊙ )
推荐byvoid大神的题解 题解链接
有大牛说这是一道单纯形法的裸题 233

说说我的理解吧
利用网络流的流量平衡建立等式,正的变量是流入该点的流量,负的变量是流出该点的流量。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define inf 2147483647
#define MAXN 1005
#define MAXM 10005
using namespace std;

int n, m, b, e, ans;
int dis[MAXN], need[MAXN], vol[MAXM][3], pre[MAXN];
bool in[MAXN];

struct node
{
    int v, w, cap;
    node *back, *next;
}edge[MAXM<<2], *adj[MAXN], *pos=edge, *temp[MAXN];

inline void add(int a,int b,int c,int d)
{
    pos->v=b, pos->w=c, pos->cap=d, pos->back=pos+1, pos->next=adj[a];
    adj[a]=pos;
    ++pos;

    pos->v=a, pos->w=-c, pos->cap=0, pos->back=pos-1, pos->next=adj[b];
    adj[b]=pos;
    ++pos;
}

bool spfa()
{
    queue<int>q;
    for(int i=b;i<=e;++i)dis[i]=inf, pre[i]=i;
    q.push(b), dis[b]=0;
    int u, v;
    while(!q.empty())
    {
        u=q.front();
        q.pop();
        in[u]=0;
        for(node *p=adj[u];p!=NULL;p=p->next)
            if(p->cap>0&&dis[(v=p->v)]>dis[u]+p->w)
            {
                dis[v]=dis[u]+p->w;
                pre[v]=u, temp[v]=p;
                if(!in[v])q.push(v), in[v]=1;
            }
    }
    return dis[e]!=inf;
}

int solve()
{
    int ans=0, delta;
    while(spfa())
    {
        delta=inf;
        for(int i=e;i!=b;i=pre[i])
            delta=min(delta,temp[i]->cap);
        for(int i=e;i!=b;i=pre[i])
        {
            temp[i]->cap-=delta, temp[i]->back->cap+=delta;
            temp[i]->back->w=-temp[i]->w;
            ans+=delta*temp[i]->w;
        }
    }
    return ans;
}

void build()
{
    b=0, e=n+2;
    for(int i=1;i<=n+1;++i)
        if(need[i]-need[i-1]>=0)add(b,i,0,need[i]-need[i-1]);
        else add(i,e,0,need[i-1]-need[i]);
    for(int i=1;i<=m;++i)
        add(vol[i][0],vol[i][1]+1,vol[i][2],inf);
    for(int i=1;i<=n;++i)
        add(i+1,i,0,inf);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)scanf("%d",&need[i]);
    for(int i=1;i<=m;++i)scanf("%d%d%d",&vol[i][0],&vol[i][1],&vol[i][2]);
    build();
    printf("%d\n",solve());
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值