P3731 [HAOI2017]新型城市化

给你一张图,保证原图补图是个二分图,在原图中加入一条边能使原图最大团数至少加一的边有哪些?

最大团数指使得选中的点集两两有边的最大点数。

点数 ≤ 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(n1))

无重边,无自环。

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) (ztv)

此时若 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

证毕。

最后,dinictarjan 即可。

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值