CF292D Connected Components 解题报告

CF292D Connected Components 解题报告

1 题目链接

https://codeforces.com/problemset/problem/292/D

2 题目整理

题目 : 连接组件

题目描述

我们已经知道Polycarpus担任系统管理员的大公司。那里的计算机网络由n台计算机和m根连接几对计算机的电缆组成。换言之,计算机网络可以表示为具有n个节点和m条边的无向图。让我们用1到n的整数来索引计算机,让我们用1到m的整数来索引电缆。

Polycarpus被赋予一项重要任务——检查公司网络的可靠性。为此,Polycarpus决定在计算机网络上进行一系列k实验,其中第i个实验如下:

  • 暂时断开从 l i l_i li r i r_i ri(包含两边)的索引电缆(其他电缆保持连接)。

  • 计算此时定义计算机网络的图形中连接组件的数量。

  • 将断开的电缆与索引从 l i l_i li重新连接到 r i r_i ri(即恢复初始网络)。

帮助Polycarpus执行所有实验,并为每个实验打印定义计算机网络的图形中连接组件的数量。孤立顶点应计为单个组件。

输入格式

第一行包含两个空格分隔的整数n,m,分别对应计算机的数量和电缆的数量。

往下m行包含电缆的说明。第i行含有一对整数 x i , y i x_i,y_i xiyi,表示第i根电缆连接的计算机编号。请注意,一对计算机可以通过多条电缆连接。

下一行包含整数k-实验次数。接下来的k行包含实验的描述。第i行包含空格分隔的整数 l i , r i l_i,r_i liri -第i次实验期间Polycarpus断开的电缆范
围。

输出格式

打印k个数字,第i个数字表示在第i个实验期间定义计算机网络的图形中连接组件的数量。

样例输入1
6 5
1 2
5 4
2 3
3 1
3 6
6
1 3
2 5
1 5
5 5
2 4
3 3
样例输出1
4
5
6
3
4
2
数据范围

对于 100 % 100\% 100%的数据:

  • 2 ≤ n ≤ 500 2 \le n \le 500 2n500
  • 0 ≤ m ≤ 1 0 4 0 \le m \le 10^4 0m104
  • 1 ≤ x i , y i ≤ n 1 \le x_i, y_i \le n 1xi,yin
  • x i ≠ y i x_i \neq y_i xi=yi
  • 1 ≤ k ≤ 2 ∗ 1 0 4 1 \le k \le 2 * 10^4 1k2104
  • 1 ≤ l i , r i ≤ m 1 \le l_i, r_i \le m 1li,rim

3 题意分析

3.1 题目大意

现有一个n个点,m条边的无向图,有k次询问,每次要求断开 l i l_i li r i r_i ri的所有边后,图上剩余的连通快数量。

3.2 样例分析

如上所述。

4 解法分析

求连通块数量,很容易能想到并查集。但是并查集并不支持删除操作,所以我们要反向加边。然而,如果单纯用并查集来做这道题的话,时间复杂度为 O ( m k log ⁡ n ) O(mk\log{n}) O(mklogn),想要过这一道题很悬。

所以,我们可以再进行一些优化。

因为题目中需要删除的边一定是连续编号的,所以可以想到运用前缀和来解决。先把左右两端的前缀和数组都建立起来,到每次查询时,就把两边的点拼在一起便解决了。这样一来,时间复杂度降为了 O ( n k log ⁡ n ) O(nk\log{n}) O(nklogn)。因为n很小,所以可以通过这一道题。

AC代码

ACCode #001
// From Heart_Blue
// Rating 2425
// reason : 思路清晰,代码简洁明了,运用了class
#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
{
	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;
}
ACCode #002
// From He_Ren
// Rating 3006
// reason : 思路清晰,代码简洁易懂通俗,可读性强
#include<cstdio>
const int MAXN = 5e2 + 5;
const int MAXM = 1e4 + 5;

struct DSC
{
	int fa[MAXN];
	inline void init(int n){ for(int i=1; i<=n; ++i) fa[i]=i;}
	int find(int u){ return fa[u]==u? u: fa[u]=find(fa[u]);}
	inline void connect(int u,int v){ fa[find(u)]=find(v);}	
}pre[MAXM],suf[MAXM];

struct Edge
{
	int u,v;
}e[MAXM];

int main(void)
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; ++i)
		scanf("%d%d",&e[i].u,&e[i].v);
	
	pre[0].init(n);
	for(int i=1; i<=m; ++i)
	{
		pre[i]=pre[i-1];
		pre[i].connect(e[i].u,e[i].v);
	}
	suf[m+1].init(n);
	for(int i=m; i>=1; --i)
	{
		suf[i]=suf[i+1];
		suf[i].connect(e[i].u,e[i].v);
	}
	
	int Q;
	scanf("%d",&Q);
	while(Q--)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		DSC now=pre[l-1];
		for(int i=1; i<=n; ++i)
			now.connect(now.fa[i], suf[r+1].fa[i]);
		int cnt=1;
		for(int i=2; i<=n; ++i)
			if(now.find(i) != now.find(1))
				++cnt,
				now.connect(i,1);
		printf("%d\n",cnt);
	}
	return 0;
}
ACCode #003
// From andrey-tsb
// Rating 2010
// reason : 思路清晰,代码清奇,用到了memcpy
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cstdlib>
using namespace std;

int pref[10010][510];
int suff[10010][510];
int cur[510];
int fl[510];

int findx(int x, int* p)
{
    if (p[x] == x)
        return x;
    return p[x] = findx(p[x], p);
}

void unite(int a, int b, int *p)
{
    int pa = findx(a, p);
    int pb = findx(b, p);

    if (pa==pb)
        return;

    if (rand()&1)
        p[pa] = pb;
    else
        p[pb] = pa;
}

int main()
{

//    freopen("/home/stranger/qworkspace/Test3/input.txt","r",stdin);

    int n,m,k;
    cin >> n >> m;
    vector<pair<int,int> > mas(m);
    for (int i=0;i<m;i++)
    {
        cin >> mas[i].first >> mas[i].second;
        mas[i].first--;
        mas[i].second--;
    }

    for (int i=0;i<n;i++)
        suff[m+1][i] = pref[0][i] = i;

    for (int i=1;i<=m;i++)
    {
        memcpy(pref[i], pref[i-1], sizeof(pref[i]) );
        unite(mas[i-1].first, mas[i-1].second, pref[i]);
    }

    for (int i=m;i>=1;i--)
    {
        memcpy(suff[i], suff[i+1], sizeof(suff[i]));
        unite(mas[i-1].first, mas[i-1].second, suff[i]);
    }

    cin >> k;
    for (int i=0;i<k;i++)
    {

        int a,b;
        cin >> a >> b;

        memcpy(cur, pref[a-1], sizeof(cur));

        for (int j=0;j<n;j++)
            unite(j, findx(j, suff[b+1]), cur);

        int cnt = 0;
        for (int j=0;j<n;j++)
        {
            int pr = findx(j, cur);
            if (!fl[pr])
                cnt++;
            fl[pr] = true;
        }

        memset(fl, 0, sizeof(fl));
        cout << cnt << "\n";

    }

    return 0;
}

5 结语

update 2022.7.21
这里,提一下并查集的时间复杂度。经过优化(路径压缩,按秩合并)的并查集单次时间复杂度是 O ( log ⁡ n ) O(\log{n}) O(logn),而不加优化的并查集是 O ( n ) O(n) O(n),所以这题是需要优化的 Q w Q QwQ QwQ(不加优化 O ( n 2 k ) O(n^2k) O(n2k)你可以试试看:-))
此题似乎还有莫队的做法,但蒟蒻无能为力,只好放一个链接了。
最后的最后,我要感谢一个大佬yh2021SYXMZ和我一起讨论关于并查集的时间复杂度问题,指正了我博客中时间复杂度的错误。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值