题解:给出一个二分图,问你取点哪个点会使得二分图匹配数减少。
解法:其实就是问二分图匹配的必须点。先对初始二分图做一次最大匹配。
现在考虑左边点,看残余网络上的边重新构图:如果是匹配边那么就从右往左连边,如果是非匹配边就从左往右连边。然后从每个非匹配点出发dfs标记每一个访问过的点。最后是匹配点且未被访问过的就是必须点。为什么呢?仔细考虑新构的图,从未匹配点出发的其实也是一条增广路,如果这条增广路能访问到 已匹配点 。那么我们就可以把这条增广路取反得到另一种匹配方案,也就是说这个被访问到的 已匹配点是非必须的。
右边点原理相似。
细节详见代码以及注释:
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; const int N=20000+10; const int M=200000+100; const int INF=0x3f3f3f3f; int n1,n2,m,s,t,id[M<<1]; struct edge{ int nxt,to,cap; }edges[M<<1]; int cnt=1,x[M],y[M],head[N],cur[N]; int add_edge(int x,int y,int z) { edges[++cnt].nxt=head[x]; edges[cnt].to=y; edges[cnt].cap=z; head[x]=cnt; return cnt; } int dep[N]; queue<int> q; bool bfs() { while (!q.empty()) q.pop(); memset(dep,0,sizeof(dep)); dep[s]=1; q.push(s); while (!q.empty()) { int x=q.front(); q.pop(); for (int i=head[x];i;i=edges[i].nxt) { edge e=edges[i]; if (!dep[e.to] && e.cap) { dep[e.to]=dep[x]+1; q.push(e.to); } } } return dep[t]; } int dfs(int x,int lim) { if (x==t) return lim; for (int& i=cur[x];i;i=edges[i].nxt) { edge e=edges[i]; if (dep[x]+1==dep[e.to] && e.cap) { int flow=dfs(e.to,min(lim,e.cap)); if (flow>0) { edges[i].cap-=flow; edges[i^1].cap=+flow; return flow; //找到一条增广路就要return } } } return 0; } int Dinic() { int maxflow=0; while (bfs()) { for (int i=s;i<=t;i++) cur[i]=head[i]; //当前弧优化 while (int flow=dfs(s,INF)) maxflow+=flow; } return maxflow; } bool vis[N],match[N]; vector<int> G[N]; bool dfs2(int x) { vis[x]=1; for (int i=0;i<G[x].size();i++) { int y=G[x][i]; if (!vis[y]) dfs2(y); } } int main() { scanf("%d%d%d",&n1,&n2,&m); s=0; t=n1+n2+1; for (int i=1;i<=m;i++) { scanf("%d%d",&x[i],&y[i]); id[i]=add_edge(x[i],y[i]+n1,1); add_edge(y[i]+n1,x[i],0); } for (int i=1;i<=n1;i++) add_edge(s,i,1),add_edge(i,s,0); for (int i=1;i<=n2;i++) add_edge(n1+i,t,1),add_edge(t,n1+i,0); Dinic(); //重新构图 for (int i=1;i<=n1+n2;i++) vis[i]=0,match[i]=0,G[i].clear(); for (int i=1;i<=m;i++) if (edges[id[i]].cap) G[x[i]].push_back(n1+y[i]); //不匹配边左到右连边 else G[n1+y[i]].push_back(x[i]),match[x[i]]=1; //匹配边右到左连边 for (int i=1;i<=n1;i++) if (!match[i]) dfs2(i); //从每个未匹配点出发dfs for (int i=1;i<=n1;i++) if (match[i] && !vis[i]) printf("%d\n",i); //是匹配点且未被 未匹配点 访问的 for (int i=1;i<=n1+n2;i++) vis[i]=0,match[i]=0,G[i].clear(); for (int i=1;i<=m;i++) if (edges[id[i]].cap) G[n1+y[i]].push_back(x[i]); //不匹配边右到左连边 else G[x[i]].push_back(n1+y[i]),match[n1+y[i]]=1; //匹配边左到右连边 for (int i=1;i<=n2;i++) if (!match[n1+i]) dfs2(n1+i); //从每个未匹配点出发dfs for (int i=1;i<=n2;i++) if (match[n1+i] && !vis[n1+i]) printf("%d\n",i); //是匹配点且未被 未匹配点 访问的 return 0; }
BZOJ 3546 [ONTAK2010]Life of the Party (二分图最大匹配必须点)
原文:https://www.cnblogs.com/clno1/p/10837407.html