洛谷 P3153 [CQOI2009]跳舞 (网络流)

传送门

一般网络流的题都难在建图,这个体可以说是网络流比较典型的建图问题了。等以后我刷够了足够的题后也许会归纳一下网络流建图里遇到的一些经典模型。

读完题后总结一下条件

1.不会和同一个人跳舞

2.和喜欢的人跳舞的次数不受限制

3.和不喜欢的人跳舞的次数不能超过 k

根据这些条件我们来构图,

对于一个男生节点 Bi,我们给他扩展两个节点:yes 节点 Byi,no 节点 Bni,每个女生同理有 Gi,Gyi,Gni

首先每个 Bi 引一条流量为 inf 的边到 Byi,代表男生愿意和无限个自己喜欢的人跳舞

每个 Bi 引一条流量为 k 的边到 Bni,代表男生愿意和 k 个自己不喜欢的人跳舞

同理,从 Gy引一条 inf 的边到 Gi,从 Gni 引一条 k 的边到 Gi

然后思考如何求出每个人都能跳的最大次数,

首先当然不能直接从原点对每个 Bi 建一条 inf 边,每个 Gi 到汇点建一条 inf 边跑最大流,这样有些人跳的较少,有的人跳的较多,答案肯定就错啦。

正确方法应该是二分源点到每个 Bi 和 每个 Gi 到汇点的流量 m,然后以此建边,跑最大流,如果此时能够满流(maxflow == n*m)就说明每个人跳 m 次是可行的

==============血的教训===============

为什么省选数据会出现 char 读入就会 wa 的情况啊,我用 char 来读入那个字符矩阵 wa 了一片,白费了我两个小时来调。。。

 

 ============================血的教训===========================

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <queue>
using namespace std;
const int inf=0x3f3f3f3f;
int n,k,s,t,g[55][55];
int head[410],to[3200010],nxt[3200010],val[3200010],tot=1,dep[410];
queue<int> que;

void add(int u,int v,int w){to[++tot]=v;val[tot]=w;nxt[tot]=head[u];head[u]=tot;}

bool bfs(){
    memset(dep,0,sizeof(dep));
    while(!que.empty()) que.pop();
    dep[s]=1;que.push(s);
    while(!que.empty()){
        int u=que.front();que.pop();
        for(int i=head[u];i;i=nxt[i]){
            if(val[i]&&!dep[to[i]]){
                dep[to[i]]=dep[u]+1;
                que.push(to[i]);
                if(to[i]==t) return 1;
            }
        }
    }
    return 0;
}

int dfs(int u,int flow){
    if(u==t) return flow;
    int rest=flow,k;
    for(int i=head[u];rest&&i;i=nxt[i]){
        if(val[i]&&dep[to[i]]==dep[u]+1){
            k=dfs(to[i],min(rest,val[i]));
            if(!k) dep[to[i]]=-1;
            val[i]-=k;val[i^1]+=k;rest-=k;
        }
    }
    return flow-rest;
}

bool judge(int m){
    //每次二分验证答案时要重新构图 
    memset(head,0,sizeof(head));tot=1;
    for(int i=1;i<=n;i++){
        add(s,i,m),add(i,s,0);   //源点向男生节点连流量为 m 的边 
        add(i,i+n,inf),add(i+n,i,0); //男生向自己的 yes 节点连流量无限的边 
        add(i,i+2*n,k),add(i+2*n,i,0); //男生想自己的 no 节点连流量为 k 的边 
        add(i+4*n,i+3*n,inf),add(i+3*n,i+4*n,0); //女生的 yes 节点向自己连流量无限的边 
        add(i+5*n,i+3*n,k),add(i+3*n,i+5*n,0); //女生的 no 节点向自己连流量为 k 的边 
        add(i+3*n,t,m),add(t,i+3*n,0); //女生节点向汇点连流量为 m 的边 
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(g[i][j]) add(i+n,j+4*n,1),add(j+4*n,i+n,0); //如果男 i 与女 j 相互喜欢,就从男 i 的 yes 节点连一条流量为 1 的边到女 j 的 yes 点 
            else add(i+2*n,j+5*n,1),add(j+5*n,i+2*n,0); //如果男 i 与女 j 相互不喜欢,就从男 i 的 no 点到女 j 的 no 点连一条流量为 1 的边。  
    int maxflow=0;
    while(bfs()){
        int flow;
        while(flow=dfs(s,inf)) maxflow+=flow;
    } 
    if(maxflow==n*m) return 1;
    else return 0;
    //跑一遍最大流,如果此时满流,那么就是每人都能跳 m 次 
}

int main(){
    scanf("%d%d",&n,&k);
    s=0,t=6*n+1;
    for(int i=1;i<=n;i++){
        char s[1000];
        scanf("%s",s+1);  //数据真的坑,不用字符串的话会 wa 
        for(int j=1;j<=n;j++){
            if(s[j]=='Y') g[i][j]=1;
            else g[i][j]=0;
        }
    }
    int l=0,r=n+k;
    while(l<r){
        int mid=(l+r+1)/2;
        if(judge(mid)) l=mid;
        else r=mid-1;
    }
    cout<<l<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/BakaCirno/p/11719228.html