CF292D Connected Components 解题报告

CF292D Connected Components 解题报告

1 题目链接

传送门

2 题目大意

题目 : 连接组件
题目大意 :

给定一个有 n n n个点 m m m条边的无向图。有 k k k次询问,每次要求断开连接着 l i l_i li r i r_i ri的边,并输出此时剩下的连通块数量(询问后的断开会恢复)。

3 解法分析

看到题时的内心OS : 这是搜索?搜索能做?我是掉并查集坑里了吗
这道题有个极其重要的点:
n ∈ [ 2 , 500 ] n \in [2,500] n[2,500]
m ∈ [ 1 , 1 0 4 ] m \in [1,10^4] m[1,104]
k ∈ [ 1 , 2 × 1 0 4 ] k \in [1,2 \times 10^4] k[1,2×104]
跟据我多年来的经验, O ( n m k ) O(nmk) O(nmk)的复杂度是可以卡过去的 (在座的大佬别学本蒟蒻) ,所以说 d f s dfs dfs完全是可以水过去的。

但是,我作为一个垃圾 O i e r Oier Oier,显然是不满足于这个时间复杂度(主要是怕 H a c k Hack Hack),通过对上一题的回忆,这道题还是可以尝试使用并查集这一数据结构来优化时间复杂度,可以直接优化至 O ( m k log ⁡ n ) O(mk\log{n}) O(mklogn)
然而,这个时间复杂度想过 1 e 4 1e4 1e4级别的数据还是有些许困难,所以我们可以尝试优化并查集。

本蒟蒻在刚开始看这道题时,以为断开了就断开了(都是英语的锅),甚至为此WA了一次。本蒟蒻的第一次提交时不知是怎么想的,竟用两个线段树分别维护前、后缀和,不过仔细想想还是有一定道理的 : 就相当于直接确定了 l i l_i li r i r_i ri后的两边情况。

通过刚才的方法,我们可以引申出一个复杂度更优的方法 : 用前后缀和来维护,每次询问时只需要将 [ 1 , l i ] [1,l_i] [1,li] [ r i + 1 , m ] [r_i+1,m] [ri+1,m]区间连接计算连通块个数即可。 其中时间复杂度为 O ( n k log ⁡ n ) O(nk\log{n}) O(nklogn)
简要说明可行性 : 每个区间只是在每次询问后不计这个区间的边,也就相当于每次询问都是相互独立的,即任何一段区间的连通块个数和连边情况都是不会变的,因此这个方法完全可行。

但是凭借多年翻阅 c s d n csdn csdn上奇妙算法的成果,这题还有一种不为人知(至少我没翻到)的做法 : 莫队算法中的回滚莫队。(本蒟蒻刚看完莫涛神犇的知乎
但是,这道题又不是正宗的回滚莫队,因为并查集不支持删除,所以我们需要作一点变通:我们让右指针 r r r往回移而不是从块的右边往右移,而且如果一个询问在块内,我们不需要单独暴力处理,直接一起处理

4 解法总结

4.1 d f s dfs dfs

O ( n m k ) O(nmk) O(nmk)暴搜即可。

4.2 不带优化的并查集

O ( m k log ⁡ n ) O(mk\log{n}) O(mklogn)暴力即可。注意这里需要反向加边

4.3 前、后缀和优化并查集

O ( n k log ⁡ n ) O(nk\log{n}) O(nklogn)的时间复杂度很轻松就可以过。(这种舍空间求时间的方法何乐不为?)

4.4 回滚莫队优化并查集

时间复杂度貌似是 O ( max ⁡ ( n m 5 3 , k ) ) O(\max (nm^{\frac{5}{3}},k)) O(max(nm35,k))(没错我自己也不知道为什么没有 k k k)。
补充说明 : 而且在块与块的转移之间,假设当前刚刚做完第 i i i块,那么我们就需要将 [ 1 , ( i − 1 ) × b l o c k + 1 ] [1,(i-1)\times block+1] [1,(i1)×block+1]里面的所有连边操作完成。因为 n ⩽ 500 n\leqslant500 n500,所以不会 T L E TLE TLE

5 AC Code

Dalao代码 #001
// From aarr
// Rating 2425
#include <iostream>
#include <vector>
using namespace std;

const int N = 505;
int l, r, nparts;

vector <pair <int, int> > adj[N];
bool mark[N];
void dfs(int v) {
	mark[v] = true;
	for (auto p : adj[v]) {
	//	int u = adj[v][i].first, x = adj[v][i].second;
		if (!(l <= p.second && p.second <= r) && !mark[p.first])
			dfs(p.first);
	}
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int u, v;
		cin >> u >> v;
		adj[u].push_back({v, i});
		adj[v].push_back({u, i});
	}
	int k;
	cin >> k;
	for (int i = 0; i < k; i++) {
		cin >> l;
		cin >> r;
		nparts = 0;
		for (int j = 1; j <= n; j++) {
			if (!mark[j]) {
				dfs(j);
				nparts++;
			}
		}
		for (int j = 1; j <= n; j++) {
			mark[j] = false;
		}
		cout << nparts << "\n";
	}
	return 0;
}//纯dfs做法
Dalao代码 #002
// From Heart_Blue
// Rating 2425
#include <cstdlib>
#include <cctype>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <fstream>
#include <numeric>
#include <iomanip>
#include <bitset>
#include <list>
#include <stdexcept>
#include <functional>
#include <utility>
#include <ctime>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
#define MEM(a,b) memset((a),(b),sizeof(a))
const LL INF = 1e9 + 7;
const int N = 2e5 + 10;
pair<int, int> p[N];
class UnionFind//xs NOIP根本用不了
{
	int p[N];
public:
	void init(int n = N)
	{
		memset(p, -1, sizeof(int)*n);//指针一向学得不好的本蒟蒻看不懂  盲猜就是单纯地赋值
	}
	int Find(int x)
	{
		int s = x;
		while (p[s] >= 0) s = p[s];
		while (x != s)
		{
			int t = p[x];
			p[x] = s;
			x = t;
		}
		return s;
	}
	void Union(int x, int y)
	{
		int px = Find(x);
		int py = Find(y);
		if (p[px] > p[py]) swap(px, py);
		p[px] += p[py];
		p[py] = px;
	}

} uf;
int res[N];
vector<pair<int, int>> vp[N];
int main()
{
	//freopen("input.txt", "r", stdin);
	//freopen("output.txt", "w", stdout);
	int n, m;
	scanf("%d%d", &n, &m);
	int lo = n;
	uf.init(n + 1);
	for (int i = 1; i <= m; i++)
	{
		scanf("%d%d", &p[i].first, &p[i].second);
		if (uf.Find(p[i].first) != uf.Find(p[i].second))
			uf.Union(p[i].first, p[i].second), lo--;
	}

	int q;
	scanf("%d", &q);
	for(int i =1; i <= q; i++)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		vp[l].push_back({ r,i });
	}
	for (int i = 1; i <= m; i++)
	{
		if (vp[i].empty()) continue;
		uf.init(n + 1);
		sort(vp[i].begin(), vp[i].end());
		int ans = n;
		for (int j = 1; j < i; j++)
		{
			int x, y;
			tie(x, y) = p[j];
			if (uf.Find(x) != uf.Find(y))
				uf.Union(x, y), ans--;
		}
		int cur = m;
		while (!vp[i].empty())
		{
			int r, pos;
			tie(r, pos) = vp[i].back();
			vp[i].pop_back();
			while (cur > r && ans != lo)
			{
				if (uf.Find(p[cur].first) != uf.Find(p[cur].second))
					uf.Union(p[cur].first, p[cur].second), ans--;
				cur--;
			}
			res[pos] = ans;
		}
	}
	for (int i = 1; i <= q; i++) printf("%d\n", res[i]);
	return 0;
}//并查集(无优化
蒟蒻代码 #001
#include <bits/stdc++.h>
using namespace std;

bool vis[507];
int n, m, l, r, q;
vector <pair <int, int> > v[507];

void dfs(int x) {
	vis[x] = 1;
	for (register int i = 0; i < v[x].size(); ++i) {
		if (v[x][i].second >= l && v[x][i].second <= r)
			continue;
		if (vis[v[x][i].first])
			continue;
        dfs(v[x][i].first);
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (register int i = 0, a, b; i < m; ++i) {
		scanf("%d%d", &a, &b);
		v[--a].push_back(make_pair(--b, i));
		v[b].push_back(make_pair(a, i));
	}
	scanf("%d", &q);
	while (q--) {
		register int ans = 0;
		scanf("%d%d", &l, &r);
		--l;
		--r;
		for (register int i = 0; i < n; ++i)
			vis[i] = 0;
		for (register int i = 0; i < n; ++i)
			if (!vis[i]) {
				++ans;
				dfs(i);
			}
		printf("%d\n", ans);
	}
	return 0;
}//纯dfs做法
蒟蒻代码 #002
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f 
#define M 10007
#define N 507
using namespace std;

int n, m;
int x[M], y[M];

struct node {
    int f[N], cnt;
    node() {
    	for (register int i = 1; i < N; ++i)
			f[i] = i;
    }
    int find(int x) {
    	return f[x] == x ? x : f[x] = find(f[x]);
    }
	void merge(int x, int y) {
		x = find(x);
		y = find(y);
		if (x != y)
			f[x] = y, ++cnt;
	}
} pre[M], suf[M];

int main() {
    scanf("%d%d", &n, &m);
    for (register int i = 1; i <= m; ++i) {
    	scanf("%d%d", &x[i], &y[i]);
		pre[i] = pre[i - 1];
		pre[i].merge(x[i], y[i]); 
    }
    for (register int i = m; i; --i) {
		suf[i] = suf[i + 1];
		suf[i].merge(x[i], y[i]);
	}
    register int q;
    scanf("%d", &q);
    while (q--) {
        register int l, r;
        scanf("%d%d", &l, &r);
        register node dsu = pre[l - 1];
        for (register int i = 1; i <= n; ++i)
            if (suf[r + 1].f[i])
				dsu.merge(i, suf[r + 1].f[i]);
        printf("%d\n", n - dsu.cnt);
    }
    return 0;
}//前、后缀和优化并查集 
蒟蒻代码 #003
#include <bits/stdc++.h>
#define rep(i, a, b) for (register int (i) = (a); (i) <= (b); ++(i))
#define K 20007
#define M 10007
#define N 507
using namespace std;

int n, m, k;
int ans[K], fa2[N], fa3[N];
int total, block, bnum;
int x[M], y[M], ys[M];

struct node {
	int l, r, id;
} q[K];

inline int read() {
	register int sum = 0, fh = 1;
	register char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-')
			fh = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		sum = (sum << 3) + (sum << 1) + (ch ^ 48);
		ch = getchar();
	}
	return sum * fh;
}

bool cmp(const node &fir, const node &sec) {
	if (ys[fir.l] ^ ys[sec.l])
		return ys[fir.l] < ys[sec.l];
	return fir.r > sec.r;
}

int gffa3(int x) {
	return (fa3[x] == x) ? x : fa3[x] = gffa3(fa3[x]);
}
void hbfa3(int r) {
	if (gffa3(x[r]) != gffa3(y[r]))
		fa3[fa3[x[r]]] = fa3[y[r]];
}

int main() {
	n = read(), m = read();
	block = ceil(pow(m, 2.0 / 3.0));
	bnum = ceil((double)m / block);
	rep(i, 1, m)
		x[i] = read(), y[i] = read();
	rep(i, 1, m)
		ys[i] = (i - 1) / block + 1;
	k = read();
	rep(i, 1, k) {
		q[i].l = read(), q[i].r = read();
		q[i].id = i;
	}
	rep(i, 1, n)
		fa3[i] = i;
	sort(q + 1, q + k + 1, cmp);
	register int j = 1;
	rep(i, 1, bnum) {
		register int l = (i - 1) * block + 1, r = m;
		while (j <= k) {
			if (ys[q[j].l] > i)
				break;
			while (r > q[j].r)
				hbfa3(r--);
			rep(p, 1, n)
				fa2[p] = fa3[p];//复制一份
			while (l < q[j].l)
				hbfa3(l++);
			register int total = 0;
			rep(p, 1, n)
				if (gffa3(p) == p)
					++total;
			ans[q[j].id] += total;
			l = (i - 1) * block + 1;
			rep(p, 1, n)
				fa3[p] = fa2[p];//回复数组
			++j;
		}
		rep(p, 1, n)
			fa3[p] = p;
		rep(p, 1, min(m, i * block))
			hbfa3(p);
	}
	rep(i, 1, k)
		printf("%d\n", ans[i]);
	return 0;
}//回滚莫队优化并查集 

6 总结

这道题可以用的方法相当多啊 : 搜索、数据结构、算法优化……(应该还是有只不过本蒟蒻没有想到)
相关的资料真的是好多(光是莫队我就打开了4、5个网页)。
天知道这篇题解我用了多久。。五个小时(确信)。
最后要感谢大佬@yh2021shx 愿意与本蒟蒻讨论并查集与前、后缀和优化的时间复杂度及其相关问题。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值