给你一张图,保证原图补图是个二分图,在原图中加入一条边能使原图最大团数至少加一的边有哪些?
最大团数指使得选中的点集两两有边的最大点数。
点数 ≤ 1 0 4 \leq 10^4 ≤104, 0 ≤ 0\leq 0≤ 边数 ≤ min ( 1.5 × 1 0 5 , n ( n − 1 ) 2 ) \leq \min(1.5\times 10^5,\frac{n(n-1)}{2}) ≤min(1.5×105,2n(n−1))。
无重边,无自环。
sol
由于补图的性质比较优秀,考虑处理补图。
显然,当补图为二分图时,原图最大团数 = = = 补图最大点独立集数。
且最大点独立集数 = = = 总点数 − - − 最小点覆盖数 = = = 总点数 − - − 最大匹配数。
问题进一步变成了在补图里删去一条边使得补图的最大匹配数至少减少一。
显然,我们需要找到最大匹配的必须边。
根据定理,必须边的判定条件是: ( x , y ) ( x , y ) (x,y) 流量为 1 1 1, 并且 x , y x , y x,y 两点在残量网络中属于不同的 Scc \operatorname{Scc} Scc。
证明:
若 z z z 是当前非匹配点,则 ( z , t ) (z,t) (z,t) 的剩余流量必定为 1 1 1。
若 v v v 是当前匹配点,则 ( v , t ) (v,t) (v,t) 的剩余流量必定为 0 0 0, ( t , v ) (t,v) (t,v) 的剩余流量必定为 1 1 1。
若 ( u , v ) (u,v) (u,v) 是当前匹配边,则 ( v , u ) (v,u) (v,u) 的剩余流量必定为 0 0 0, ( u , v ) (u,v) (u,v) 的剩余流量必定为 1 1 1。
换言之,残量网络中存在路径 ( ⋯ → z → t → v ⋯ ) (\cdots \to z \to t \to v \cdots) (⋯→z→t→v⋯)。
此时若 u u u 到 z z z 有增广路,则残量网络上 u u u 能到达 z z z, z z z 能到达 t t t, t t t 能到达 v v v, v v v 能到达 u u u,所以此时 u , v u,v u,v 处在残量网络上的同一个 Scc \operatorname{Scc} Scc 内。
所以,必须边的判定条件是: ( x , y ) ( x , y ) (x,y) 流量为 1 1 1, 并且 x , y x , y x,y 两点在残量网络中属于不同的 Scc \operatorname{Scc} Scc。
证毕。
最后,dinic
套 tarjan
即可。
#include <bits/stdc++.h>
using namespace std;
inline int read()
{
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9')
{
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9')
{
x = x * 10 + c - '0';
c = getchar();
}
return x * f;
}
inline void write(int x)
{
if(x < 0)
{
putchar('-');
x = -x;
}
if(x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
const int _ = 1e4 + 10, M = 2e5 + 10;
int n, m, s, t, t1[_], t2[_], lv[_], cur[_];
int tot = 1, head[_], to[M << 1], nxt[M << 1], w[M << 1];
vector<int> D[_];
int dfn[_], low[_], tim, id[_], cnt;
stack<int> st;
int col[_];
vector<pair<int, int>> Ans;
inline void add(int u, int v, int dis)
{
to[++tot] = v;
nxt[tot] = head[u];
w[tot] = dis;
head[u] = tot;
}
inline bool bfs()
{
memset(lv, -1, sizeof(lv));
lv[s] = 0;
memcpy(cur, head, sizeof(head));
queue<int> q;
q.push(s);
while (!q.empty())
{
int p = q.front();
q.pop();
for (int eg = head[p]; eg; eg = nxt[eg])
{
int v = to[eg], vol = w[eg];
if (vol > 0 && lv[v] == -1)
lv[v] = lv[p] + 1, q.push(v);
}
}
return lv[t] != -1;
}
int dfs(int p = s, int flow = INT_MAX)
{
if (p == t)
return flow;
int rmn = flow;
for (int eg = cur[p]; eg && rmn; eg = nxt[eg])
{
cur[p] = eg;
int v = to[eg], vol = w[eg];
if (vol > 0 && lv[v] == lv[p] + 1)
{
int c = dfs(v, min(vol, rmn));
rmn -= c;
w[eg] -= c;
w[eg ^ 1] += c;
}
}
return flow - rmn;
}
inline int dinic()
{
int ans = 0;
while (bfs())
ans += dfs();
return ans;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++tim;
st.push(u);
for(int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if(!w[i]) continue;
if(!dfn[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(!id[v])
{
low[u] = min(low[u], dfn[v]);
}
}
if(dfn[u] == low[u])
{
++cnt;
while(1)
{
int now = st.top();
st.pop();
id[now] = cnt;
if(now == u) break;
}
}
}
void pre(int u, int c)
{
col[u] = c;
for(int v : D[u])
if(!col[v]) pre(v, c ^ 1);
}
signed main()
{
n = read(), m = read();
s = n + 1, t = n + 2;
for(int i = 1, u, v; i <= m; ++i)
{
u = read(), v = read();
D[u].push_back(v);
D[v].push_back(u);
}
for(int i = 1; i <= n; ++i)
{
if(!col[i]) pre(i, 2);
}
tot = 1;
memset(head, 0, sizeof head);
for(int i = 1; i <= n; ++i)
if(col[i] == 2)
{
add(s, i, 1), add(i, s, 0);
for(int v : D[i])
{
add(i, v, 1);
add(v, i, 0);
}
}
else
add(i, t, 1), add(t, i, 0);
int res = dinic();
for(int i = 1; i <= t; ++i)
if(!dfn[i]) tarjan(i);
for(int u = 1; u <= n; ++u)
if(col[u] == 2)
for(int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if(w[i]) continue;
if(v == s || v == t) continue;
if(id[u] != id[v])
Ans.push_back(u < v ? make_pair(u, v) : make_pair(v, u));
}
sort(Ans.begin(), Ans.end());
write(Ans.size());
putchar('\n');
for(auto v : Ans)
write(v.first), putchar(' '), write(v.second), putchar('\n');
return 0;
}