[BZOJ 1305] 跳舞

Link:https://www.lydsy.com/JudgeOnline/problem.php?id=1305

Solution:

发现res是否可行具有单调性,二分答案

容易看出每次check(mid)用网络流判断,关键在于建图:

1)将每一个人拆成两个点,男孩的两个点为X1,X2,女孩为Y1,Y2

2)将相互喜欢的将X1,Y1相连,互相讨厌的将X2,Y2相连,容量为1

3)每一个X1向X2连一条容量为k的边,每一个Y2向Y1连一条容量为k的边

4)从源点向每一个X1连容量mid的边,从每一个Y1向汇点连容量为mid的边

 

这里建图的难点还是在拆点上,

拆点的目的在于将从该点出发的流量分类,并能对每一类进行容量限制

EX:为了使向“讨厌的”流量不超过k,我们将Xi流向“讨厌的”节点的流量专门设一个源点(分类)

这样将 Xi1  ---->   Xi2 的容量设为k即可满足这一条件(加以容量限制)

 

于是当遇到每个点根据不同流向的流量要加以不同限制时,

考虑将点拆成好几个,再对每个点建一个总源点(此题没有必要,只有2类)

 

Code:

#include <bits/stdc++.h>

using namespace std;

const int MAXN=200+10;
const int INF=1<<27;

struct edge
{
    int to,cap,rev;
};
vector<edge> G[MAXN];

char dat[MAXN][MAXN];
int n,k,S,T,level[MAXN],iter[MAXN];

void add_edge(int from,int to,int cap)
{
    G[from].push_back(edge{to,cap,G[to].size()});
    G[to].push_back(edge{from,0,G[from].size()-1});
}

bool bfs()
{
    memset(level,-1,sizeof(level));
    queue<int> que;que.push(S);level[S]=0;
    
    while(!que.empty())
    {
        int u=que.front();que.pop();
        for(int i=0;i<G[u].size();i++)
        {
            edge v=G[u][i];
            if(v.cap && level[v.to]==-1)
                level[v.to]=level[u]+1,que.push(v.to);
        }
    }
    return level[T]!=-1;
}

int dfs(int v,int f)
{
    if(v==T) return f;
    int ret=0;
    for(int& i=iter[v];i<G[v].size();i++)
    {
        edge &e=G[v][i];
        if(e.cap && level[e.to]==level[v]+1)
        {
            int d=dfs(e.to,min(f,e.cap));
            e.cap-=d;G[e.to][e.rev].cap+=d;ret+=d;f-=d;  //一定要加f-=d
            if(!f) break;  //这里的剪枝很重要
        }
    }
    return ret;
}

int dinic()
{
    int ret=0;
    while(bfs())
        memset(iter,0,sizeof(iter)),ret+=dfs(S,INF);
    return ret;
}

void build(int flow)
{
    for(int i=0;i<MAXN;i++) G[i].clear();
    for(int i=1;i<=n;i++) add_edge(S,i,flow),add_edge(i,i+2*n,k);
    for(int i=n+1;i<=2*n;i++) add_edge(i,T,flow),add_edge(i+2*n,i,k);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(dat[i][j]=='Y') add_edge(i,n+j,1);
            else add_edge(i+2*n,n+j+2*n,1);
}

int main()
{
    scanf("%d%d",&n,&k);
    S=0;T=4*n+1;
    for(int i=1;i<=n;i++) scanf("%s",dat[i]+1);
            
    int l=0,r=50;  //从0开始二分
    while(l<=r)
    {
        int mid=(l+r)>>1;
        build(mid);
        if(dinic()<n*mid) r=mid-1;
        else l=mid+1;
    }
    printf("%d",r);
    return 0;
}

Review:

关于Dinic的两种模板:

(1)找到一条路径便立即返回

(2)对于每个点枚举完所有情况后一起返回

由于Dinic能加上当前弧优化,所以(1)虽然开栈次数看上去多了,但效率绝对不比(2)差

而(2)如果不加上容量已为0时的剪枝,则要比(1)慢得多

注意:使用(2)时每次一定要f-=d

原文地址:https://www.cnblogs.com/newera/p/9096391.html