CF1916F Group Division

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值