tags
图论
中文题面
给定一个无向图 G G G,点数为 n 1 + n 2 n_1+n_2 n1+n2,边数为 m m m,保证 G G G 是一个点双连通分量且无重边。
请将 G G G 划分为两个无交集的点集 S 1 , S 2 S_1,S_2 S1,S2 满足 ∣ S 1 ∣ = n 1 , ∣ S 2 ∣ = n 2 |S_1|=n_1,|S_2|=n_2 ∣S1∣=n1,∣S2∣=n2,且 S 1 , S 2 S_1,S_2 S1,S2 的导出子图均连通。
保证有解
思路
设 E E E 为原图点集考虑动态构造 S S S(含 n 1 n1 n1 个元素),如果 ∃ T , T ∩ S = ∅ 且 T ∪ S = E \exists T,\ T \cap S = \empty 且 T \cup S = E ∃T, T∩S=∅且T∪S=E,每次从 T T T 中选出一个并非 T T T 的导出子图割点但与与 S S S 连通的点 ,把它从 T T T 中删除并在 S S S 中加入,进行 n 1 n1 n1 次。
我们来证明一下每次均能找出这样的点。显然, T T T 的导出子图内的每个割点都会和 S S S 内点有连边,否则会导致 E E E 非点双联通,与题意矛盾。考虑反证法,我们取出一个割点 a a a,满足将 a a a 去掉后 T T T 中至少有一个新连通块 T ′ T′ T′ 内无割点(一定能找到这样的一个联通块,因为如果选择树上深度最大的那个割点,它的子树除去它就会是一个没有割点的联通块)。由于 T ′ T′ T′ 内没有割点, T T T 中也不存在一个非割点又与 S S S 有连边的点,即: T ′ T′ T′ 在去掉 a a a 后和 S S S 不连通了,所以 a a a 是原图的一个割点,与原图点双联通矛盾,故每次操作都必然存在这样的一个点。
每次 t a r j a n tarjan tarjan 求出 T T T 的所有割点并打上标记,任取一个非割点也不属于 S S S 的与 S S S 有连边的点加入,注意 t a r j a n tarjan tarjan 的一些细节即可
代码
#include <bits/stdc++.h>
#define ALL(v) begin(v), end(v)
#define All(v, l, r) &v[l], &v[(r) + 1]
#define int long long
using namespace std;
constexpr int N = 4005;
int t, n, n1, n2, m, tot;
array<vector<int>, N> G;
array<bool, N> vis, cut;
array<int, N> dfn, low;
void tarjan(int u, int ff) {
dfn[u] = low[u] = ++tot;
int siz = 0;
for (auto v : G[u]) {
if (u == ff || vis[v]) continue;
else if (!dfn[v]) {
siz++, tarjan(v, u);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u] && (ff || siz > 1)) cut[u] = true;
}
else low[u] = min(low[u], dfn[v]);
}
return ;
}
void solve() {
cin >> n1 >> n2 >> m, n = n1 + n2;
fill(All(vis, 1, n), false);
for (int i = 1; i <= n; ++i) G[i].clear(), G[i].shrink_to_fit();
for (int i = 1, u, v; i <= m; ++i)
cin >> u >> v, G[u].emplace_back(v), G[v].emplace_back(u);
for (int r = 1; r <= n1; ++r) {
tot = 0, fill(All(dfn, 1, n), 0), fill(All(low, 1, n), 0);
fill(All(cut, 1, n), false);
for (int i = 1; i <= n; ++i)
if (!vis[i]) { tarjan(i, 0); break;}
for (int i = 1; i <= n; ++i) {
if (vis[i] || cut[i]) continue;
bool flag = false;
for (int j : G[i]) flag |= vis[j];
if (flag || r == 1) { vis[i] = true; break;}
}
}
for (int i = 1; i <= n; ++i) if (vis[i]) cout << i << ' ';
cout << "\n";
for (int i = 1; i <= n; ++i) if (!vis[i]) cout << i << ' ';
cout << "\n";
return ;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> t;
while (t--) solve();
return 0;
}