【网络流相关】网络流模型总结——加权二分图

@

引入

最近刷网络流24题时发现了一个比较通用的模型,拿出来总结一下。
Luogu P2756
Luogu P4014
Luogu P4015
Luogu P2763
对于这四道题,都可以构造成二分图模型,使用最大流或者费用流。

P2756 飞行员配对方案问题

其实这题没啥好说的……就是个二分图匹配,最后输出方案只需要枚举原二分图中的边看看哪些残量为\(0\)(也就是作为匹配边)即可。

#include<cstdio>
#include<queue>
using namespace std;
int n,m,num,cnt,u,v,head[20005],cur[20005],dis[20005],ans;
bool vis[20005];
struct data
{
    int sta,to,next,val;
}e[5000005];
void add(int u,int v,int val)
{
    e[++cnt].to=v;
    e[cnt].sta=u;
    e[cnt].next=head[u];
    head[u]=cnt;
    e[cnt].val=val;
}
bool bfs(int s,int t)
{
    queue<int> que;
    que.push(s);
    for (int i=1;i<=n+2;i++) dis[i]=0,vis[i]=false,cur[i]=head[i];
    vis[s]=true;
    dis[s]=1;
    while (!que.empty())
    {
        int now=que.front();
        que.pop();
        for (int i=head[now];i;i=e[i].next)
        {
            v=e[i].to;
            if (!vis[v]&&e[i].val>0)
            {
                dis[v]=dis[now]+1;
                vis[v]=true;
                if (v==t) return true;
                que.push(v);
            }
        }
    }
    return false;
}
int dfs(int now,int t,int flow)
{
    if (!flow||now==t) return flow;
    int used=0;
    for (int i=cur[now];i;i=e[i].next)
    {
        cur[now]=i;
        v=e[i].to;
        if (dis[now]+1!=dis[v]) continue;
        int tmp=dfs(v,t,min(flow-used,e[i].val));
        if (tmp)
        {
            e[i].val-=tmp;
            e[i^1].val+=tmp;
            used+=tmp;
            if (flow-used==0) return flow;
        }
    }
    return used;
}
void Dinic(int s,int t)
{
    while (bfs(s,t)) ans+=dfs(s,t,0x7fffffff);
}
int main()
{
    scanf("%d%d%d%d",&m,&n,&u,&v);
    cnt=1;
    while (u!=-1&&v!=-1)
    {
        add(u,v,1);
        add(v,u,0);
        scanf("%d%d",&u,&v);
    }
    for (int i=1;i<=m;i++) add(n+1,i,1),add(i,n+1,0);
    for (int i=m+1;i<=n;i++) add(i,n+2,1),add(n+2,i,0);
    Dinic(n+1,n+2);
    if (ans)
    {
        printf("%d\n",ans);
        for (int i=2;i<=cnt-2*n;i+=2) if (e[i].val==0) printf("%d %d\n",e[i].sta,e[i].to);
    }
    else printf("No Solution!");
    return 0;
}

P4014 分配问题

根据题目的描述,可以注意到每一个人只能做一个工件,每一个工件只能给一个人做。所以我们可以考虑使用二分图模型。
把人和工件当成两个点集,就可以使用二分图匹配了。
但是值得注意的是,这题引入了一个效益。
所以我们考虑对于每一个边加入一个费用,人和工件之间的边容量为\(1\),费用为\(cost[i][j]\)
然后使用EK算法跑费用流即可,最大效益使用最长路,最小效益使用最短路。

#include<cstdio>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
struct data
{
    int to,next,val,pri;
}e1[10005],e2[10005];
int head1[105],head2[105],flow[105],cost[105],pree[105],prep[105],ans,n,cnt=1;
bool vis[105];
void add(int u,int v,int w,int f)
{
    e1[++cnt].to=v;
    e1[cnt].next=head1[u];
    e1[cnt].val=w;
    e1[cnt].pri=f;
    head1[u]=cnt;
}
int SPFA_short(int s,int t)
{
    queue<int> que;
    que.push(s);
    for (int i=1;i<=t;i++) pree[i]=0,prep[i]=-1,cost[i]=inf,vis[i]=false,flow[i]=0;
    vis[s]=true,pree[s]=0,prep[s]=0,cost[s]=0,flow[s]=inf;
    while (!que.empty())
    {
        int u=que.front();
        que.pop();
        vis[u]=false;
        for (int i=head1[u];i;i=e1[i].next)
        {
            int v=e1[i].to;
            if (cost[v]>cost[u]+e1[i].pri&&e1[i].val>0)
            {
                flow[v]=min(flow[u],e1[i].val);
                prep[v]=u;
                pree[v]=i;
                cost[v]=cost[u]+e1[i].pri;
                if (!vis[v])
                {
                    que.push(v);vis[v]=true;
                }
            }
        }
    }
    if (prep[t]!=-1) return flow[t];
    else return -1;
}
int SPFA_long(int s,int t)
{
    queue<int> que;
    que.push(s);
    for (int i=1;i<=t;i++) pree[i]=0,prep[i]=-1,cost[i]=-inf,vis[i]=false,flow[i]=0;
    vis[s]=true,pree[s]=0,prep[s]=0,cost[s]=0,flow[s]=inf;
    while (!que.empty())
    {
        int u=que.front();
        que.pop();
        vis[u]=false;
        for (int i=head2[u];i;i=e2[i].next)
        {
            int v=e2[i].to;
            if (cost[v]<cost[u]+e2[i].pri&&e2[i].val>0)
            {
                flow[v]=min(flow[u],e2[i].val);
                prep[v]=u;
                pree[v]=i;
                cost[v]=cost[u]+e2[i].pri;
                if (!vis[v])
                {
                    que.push(v);vis[v]=true;
                }
            }
        }
    }
    if (prep[t]!=-1) return flow[t];
    else return -1;
}
void EK(int s,int t)
{
    int delta=SPFA_short(s,t);
    while (delta!=-1)
    {
        ans+=cost[t]*delta;
        for (int i=t;i;i=prep[i])
        {
            e1[pree[i]].val-=delta;
            e1[pree[i]^1].val+=delta;
        }
        delta=SPFA_short(s,t);
    }
    printf("%d\n",ans);
    ans=0;
    delta=SPFA_long(s,t);
    while (delta!=-1)
    {
        ans+=cost[t]*delta;
        for (int i=t;i;i=prep[i])
        {
            e2[pree[i]].val-=delta;
            e2[pree[i]^1].val+=delta;
        }
        delta=SPFA_long(s,t);
    }
    printf("%d",ans);
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
        {
            int tmp;
            scanf("%d",&tmp);
            add(i,j+n,1,tmp);
            add(j+n,i,0,-tmp);
        }
    int s=2*n+1,t=2*n+2;
    for (int i=1;i<=n;i++) add(s,i,1,0),add(i,s,0,0),add(i+n,t,1,0),add(t,i+n,0,0);
    for (int i=1;i<=t;i++) head2[i]=head1[i];
    for (int i=1;i<=cnt;i++) e2[i]=e1[i];
    EK(s,t);
    return 0;
}

P4015 运输问题

对于多个源点和多个汇点的网络流模型,一个通用的套路就是建立一个超级源点和超级汇点。
由于仓库能发送的流量是有限制的,我们考虑对超源和仓库之间的边加入一个容量限制,费用为\(0\)
这样就能够意味着仓库可以免费从超源获取一定流量,也就是实现了仓库点存储流量的功能。
同理,零售商连向汇点也应该有一个容量限制,费用自然是为\(0\)
至于仓库和零售商之间的边,流量没有任何限制,但费用应该为\(1\)

#include<cstdio>
#include<queue>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
struct data
{
    int to,next,val,pri;
}e1[5005],e2[5005];
int cnt=1,head1[505],head2[5005],cost[505],flow[505],pree[505],prep[505],n,m,minans,maxans;
bool vis[505];
void add(int u,int v,int w,int p)
{
    e1[++cnt].to=v;
    e1[cnt].next=head1[u];
    head1[u]=cnt;
    e1[cnt].val=w;
    e1[cnt].pri=p;
}
int SPFA_short(int s,int t)
{
    queue<int> que;
    for (int i=1;i<=t;i++) cost[i]=inf,pree[i]=0,prep[i]=-1,vis[i]=false;
    que.push(s);
    vis[s]=true;cost[s]=0;pree[s]=0;prep[s]=0;flow[s]=inf;
    while (!que.empty())
    {
        int u=que.front();
        que.pop();
        vis[u]=false;
        for (int i=head1[u];i;i=e1[i].next)
        {
            int v=e1[i].to;
            if (e1[i].val>0&&cost[v]>cost[u]+e1[i].pri)
            {
                cost[v]=cost[u]+e1[i].pri;
                pree[v]=i;
                prep[v]=u;
                flow[v]=min(flow[u],e1[i].val);
                if (!vis[v])
                {
                    que.push(v);
                    vis[v]=true;
                }
            }
        }
    }
    if (prep[t]!=-1) return flow[t];
    else return -1;
}
int SPFA_long(int s,int t)
{
    queue<int> que;
    for (int i=1;i<=t;i++) cost[i]=-inf,pree[i]=0,prep[i]=-1,vis[i]=false;
    que.push(s);
    vis[s]=true;cost[s]=0;pree[s]=0;prep[s]=0;flow[s]=inf;
    while (!que.empty())
    {
        int u=que.front();
        que.pop();
        vis[u]=false;
        for (int i=head2[u];i;i=e2[i].next)
        {
            int v=e2[i].to;
            if (e2[i].val>0&&cost[v]<cost[u]+e2[i].pri)
            {
                cost[v]=cost[u]+e2[i].pri;
                pree[v]=i;
                prep[v]=u;
                flow[v]=min(flow[u],e2[i].val);
                if (!vis[v])
                {
                    que.push(v);
                    vis[v]=true;
                }
            }
        }
    }
    if (prep[t]!=-1) return flow[t];
    else return -1;
}
void EK(int s,int t)
{
    int delta=SPFA_short(s,t);
    while (delta!=-1)
    {
        minans+=cost[t]*delta;
        for (int i=t;i;i=prep[i])
        {
            e1[pree[i]].val-=delta;
            e1[pree[i]^1].val+=delta;
        }
        delta=SPFA_short(s,t);
    }
    delta=SPFA_long(s,t);
    while (delta!=-1)
    {
        maxans+=cost[t]*delta;
        for (int i=t;i;i=prep[i])
        {
            e2[pree[i]].val-=delta;
            e2[pree[i]^1].val+=delta;
        }
        delta=SPFA_long(s,t);
    }
}
int main()
{
    scanf("%d%d",&m,&n);
    int s=m+n+1,t=m+n+2;
    for (int i=1;i<=m;i++)
    {
        int tmp;
        scanf("%d",&tmp);
        add(s,i,tmp,0);
        add(i,s,0,0);
    }
    for (int i=1;i<=n;i++)
    {
        int tmp;
        scanf("%d",&tmp);
        add(i+m,t,tmp,0);
        add(t,i+m,0,0);
    }
    for (int i=1;i<=m;i++)
        for (int j=1;j<=n;j++)
        {
            int tmp;
            scanf("%d",&tmp);
            add(i,j+m,inf,tmp);
            add(j+m,i,0,-tmp);
        }
    for (int i=1;i<=t;i++) head2[i]=head1[i];
    for (int i=1;i<=cnt;i++) e2[i]=e1[i];
    EK(s,t);
    printf("%d\n%d",minans,maxans);
    return 0;
}

P2763 试题库问题

其实这就是一个特殊的二分图匹配,允许某些点被选用多次。
那么我们在给源点连边的时候改一下容量限制即可。

#include<cstdio>
#include<queue>
using namespace std;
int n,k,head[50005],cur[50005],dis[50005],cnt=1;
struct data
{
    int to,next,val;
}e[50005];
void add(int u,int v,int w)
{
    e[++cnt].to=v;
    e[cnt].next=head[u];
    head[u]=cnt;
    e[cnt].val=w;
}
bool bfs(int s,int t)
{
    queue<int> que;
    que.push(s);
    for (int i=1;i<=t;i++) dis[i]=0,cur[i]=head[i];
    while (!que.empty())
    {
        int u=que.front();
        que.pop();
        for (int i=head[u];i;i=e[i].next)
        {
            int v=e[i].to;
            if (!dis[v]&&e[i].val>0)
            {
                dis[v]=dis[u]+1;
                if (v==t) return true;
                que.push(v);
            }
        }
    }
    return false;
}
int dfs(int u,int t,int flow)
{
    if (u==t||!flow) return flow;
    int used=0;
    for (int i=cur[u];i;i=e[i].next)
    {
        cur[u]=i;
        int v=e[i].to;
        if (dis[v]!=dis[u]+1) continue;
        int tmp=dfs(v,t,min(flow-used,e[i].val));
        if (!tmp) continue;
        e[i].val-=tmp;
        e[i^1].val+=tmp;
        used+=tmp;
        if (flow==used) return flow;
    }
    return used;
}
int Dinic(int s,int t)
{
    int ans=0;
    while (bfs(s,t)) ans+=dfs(s,t,0x3f3f3f3f);
    return ans;
}
int main()
{
    scanf("%d%d",&k,&n);
    int s=k+n+1,t=k+n+2,tot=0;
    for (int i=1;i<=k;i++)
    {
        int tmp;
        scanf("%d",&tmp);
        tot+=tmp;
        add(s,i,tmp);
        add(i,s,0);
    }
    for (int i=1;i<=n;i++)
    {
        int p;
        add(i+k,t,1);
        add(t,i+k,0);
        scanf("%d",&p);
        for (int j=1;j<=p;j++)
        {
            int tmp;
            scanf("%d",&tmp);
            add(tmp,i+k,1);
            add(i+k,tmp,0);
        }
    }
    if (Dinic(s,t)==tot)
    {
        for (int i=1;i<=k;i++)
        {
            printf("%d: ",i);
            for (int j=head[i];j;j=e[j].next)
            {
                if (e[j].to==s) continue;
                if (e[j].val==0) printf("%d ",e[j].to-k);
            }
            printf("\n");
        }
    }
    else printf("No Solution!");
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/notscience/p/12063773.html
今日推荐