[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