求二分图匹配的不可行边,思路书上讲的很清楚了。
在不一定完备匹配的情况下,使用最大流求解后在残量网络中求强连通分量即可。
主要是代码实现。
下面有比较详细的代码注释和封装好的 Dinic 模板。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int N = 300010;
const int M = 5000010;
int n, m, t;
bool vis[N];
vector<int> mp[N];
vector<int> ans;
int read() {
int x = 0, f = 1;
char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
struct Dinic{
int cnt = 1, s, t, INF = 1 << 29;
int head[N], d[N], now[N];
struct Edge{int nxt, to, val;} ed[M];
queue<int>q;
void add(int u, int v, int w){
ed[++ cnt] = (Edge){head[u], v, w}; head[u] = cnt;
ed[++ cnt] = (Edge){head[v], u, 0}; head[v] = cnt;
}
bool bfs(){
while(!q.empty()) q.pop();
memset(d, 0, sizeof(d));
q.push(s); d[s] = 1; now[s] = head[s];
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i; i = ed[i].nxt){
int v = ed[i].to, w = ed[i].val;
if(!d[v] && w){
now[v] = head[v];
d[v] = d[u] + 1;
if(v == t) return true;
q.push(v);
}
}
}
return false;
}
int dinic(int u, int flow){
if(u == t) return flow;
int rest = flow, i;
for(i = now[u]; i && rest; i = ed[i].nxt){
int v = ed[i].to, w = ed[i].val;
if(w && d[v] == d[u] + 1){
int k = dinic(v, min(rest, w));
if(!k) d[v] = 0;
ed[i].val -= k;
ed[i ^ 1].val += k;
rest -= k;
}
}
now[u] = i;
return flow - rest;
}
void work(int S, int T){
s = S, t = T;
int flow = 0, maxflow = 0;
while(bfs())
while(flow = dinic(s, INF)) maxflow += flow;
for(int u = s; u <= t; u ++)//建图。
for(int i = head[u]; i; i = ed[i].nxt){
int v = ed[i].to, w = ed[i].val;
if(w) mp[u].push_back(v);
else vis[i] = true;
//在残量网络中,容量为 0 说明流量为 1,反之同理。
}
}
} G;
//以上是 Dinic 板子,以下是 tarjan 板子。
int top, tot, num;
int dfn[N], low[N], st[N], c[N];
void tarjan(int u){
dfn[u] = low[u] = ++ num;
st[++ top] = u;
for(int i = 0; i < mp[u].size(); i ++){
int v = mp[u][i];
if(!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if(!c[v])
low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u]){
int v; tot ++;
do{
v = st[top --];
c[v] = tot;
}while(v != u);
}
}
int main(){
n = read(), m = read(), t = read();
int S = 0, T = n + m + 1;//超级源点和汇点。
for(int i = 1; i <= t; i ++){
int u = read(), v = read() + n;
G.add(u, v, 1);//每条边的容量均为 1。
}
for(int i = 1; i <= n; i ++) G.add(S, i, 1);
for(int i = n + 1; i <= n + m; i ++) G.add(i, T, 1);
G.work(S, T);//求最大流并建图。
for(int i = S; i <= T; i ++)
if(!dfn[i]) tarjan(i);//强连通分量。
for(int i = 2; i <= 2 * t + 1; i += 2){
int u = G.ed[i ^ 1].to, v = G.ed[i].to;//根据成对变换,依次判定每一条边。
if(!vis[i] && c[u] != c[v]) ans.push_back(i / 2);
//vis 记录一条边的流量是否为 1,c[] 记录所属强连通分量。
//若两者都不行,说明这是一条不可行边。
}
printf("%d\n", ans.size());
for(int i = 0; i < ans.size(); i ++)
printf("%d ", ans[i]);
puts("");
return 0;
}
%%%