并查集知识汇总

并查集支持两种操作:合并和查询,基础的拓展操作为记录连通块中点的数量

模板题:连通块中点的数量(本题来自acwing算法基础课活动 - AcWing

给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。

现在要进行 m 个操作,操作共有三种:

  1. C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
  2. Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
  3. Q2 a,询问点 a 所在连通块中点的数量;

输入格式

第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 C a bQ1 a b 或 Q2 a 中的一种。

输出格式

对于每个询问指令 Q1 a b,如果 a 和 b 在同一个连通块中,则输出 Yes,否则输出 No

对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量

每个结果占一行。

数据范围

1≤n,m≤105

输入样例:

5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

输出样例:

Yes
2
3

 代码如下:

#include<iostream>

using namespace std;

const int N=100010;

int n,m;
int p[N],cnt[N];

int find(int x)//查询x的根节点:一直找父节点,直到父节点=本身
{
	if(p[x]!=x)
	p[x]=find(p[x]);
	return p[x];
} 

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		p[i]=i;
		cnt[i]=1;//以 i 为根的集合初始时只有自身一个点
	}

	while(m--)
	{
		string op;
		int a,b;
		cin>>op;
		if(op=="C")
		{
			cin>>a>>b;
			a=find(a),b=find(b);
			if(a!=b)
			{
				p[a]=b;//将 a 合并到 b 中
				cnt[b]+=cnt[a];
			}
		}
		else if(op=="Q1")
		{
			cin>>a>>b;
			if(find(a)==find(b))
			puts("Yes");
			else puts("No");
		}
		else
		{
			cin>>a;
			cout<<cnt[find(a)]<<endl;
		}
	}
}

维护到根节点的距离 

有一个划分为 N 列的星际战场,各列依次编号为 1,2,…,N。

有 N 艘战舰,也依次编号为 1,2,…,N,其中第 i 号战舰处于第 i 列。

有 T 条指令,每条指令格式为以下两种之一:

  1. M i j,表示让第 i 号战舰所在列的全部战舰保持原有顺序,接在第 j 号战舰所在列的尾部。
  2. C i j,表示询问第 i 号战舰与第 j 号战舰当前是否处于同一列中,如果在同一列中,它们之间间隔了多少艘战舰。

现在需要你编写一个程序,处理一系列的指令。

输入格式

第一行包含整数 T,表示共有 T 条指令。

接下来 T 行,每行一个指令,指令有两种形式:M i j 或 C i j

其中 M 和 C 为大写字母表示指令类型,i 和 j 为整数,表示指令涉及的战舰编号。

输出格式

你的程序应当依次对输入的每一条指令进行分析和处理:

如果是 M i j 形式,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息;

如果是 C i j 形式,你的程序要输出一行,仅包含一个整数,表示在同一列上,第 i 号战舰与第 j 号战舰之间布置的战舰数目,如果第 i 号战舰与第 j 号战舰当前不在同一列上,则输出 −1。

数据范围

N ≤ 30000,T ≤ 500000

输入样例:

4
M 2 3
C 1 2
M 2 4
C 4 2

输出样例:

-1
1

#include<cstdio> 
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=30010;
int m;
int p[N],siz[N],d[N];
int find(int x)
{
    if(p[x]!=x)
    {
        int root=find(p[x]);
        d[x]+=d[p[x]];
        p[x]=root;
    }
    return p[x];
}
int main()
{
    scanf("%d",&m);
    for(int i=1;i<N;i++)
    {
        p[i]=i;
        siz[i]=1;
    }
    while(m--)
    {
        char op[2];
        int a,b;
        scanf("%s%d%d",op,&a,&b);
        if(op[0]=='M')
        {
            int pa=find(a),pb=find(b);
            if(pa!=pb)
            {d[pa]=siz[pb];
            siz[pb]+=siz[pa];
            p[pa]=pb;
            }
        }
        else
        {
            int pa=find(a),pb=find(b);
            if(pa!=pb)
            puts("-1");
            else
            printf("%d\n",max(0,abs(d[a]-d[b])-1));
        }
    }
}

作者:小豆点心
链接:https://www.acwing.com/activity/content/code/content/3673748/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 因为发现自己没写这道题,就把y总的copy过来了

以一道题分别介绍带边权和拓展域问题

奇偶游戏

小 A 和小 B 在玩一个游戏。

首先,小 A 写了一个由 0 和 1 组成的序列 S,长度为 N。

然后,小 B 向小 A 提出了 M 个问题。

在每个问题中,小 B 指定两个数 l 和 r,小 A 回答 S[l∼r] 中有奇数个 1 还是偶数个 1。

机智的小 B 发现小 A 有可能在撒谎。

例如,小 A 曾经回答过 S[1∼3] 中有奇数个 1,S[4∼6] 中有偶数个 1,现在又回答 S[1∼6] 中有偶数个 1,显然这是自相矛盾的。

请你帮助小 B 检查这 M 个答案,并指出在至少多少个回答之后可以确定小 A 一定在撒谎。

即求出一个最小的 k,使得 01 序列 S 满足第 1∼k 个回答,但不满足第 1∼k+1 个回答。

输入格式

第一行包含一个整数 N,表示 01 序列长度。

第二行包含一个整数 M,表示问题数量。

接下来 M 行,每行包含一组问答:两个整数 l 和 r,以及回答 even 或 odd,用以描述 S[l∼r] 中有偶数个 1 还是奇数个 1。

输出格式

输出一个整数 k,表示 01 序列满足第 1∼k 个回答,但不满足第 1∼k+1 个回答,如果 01 序列满足所有回答,则输出问题总数量。

数据范围

N ≤ 109,M ≤ 5000

输入样例:

10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd

输出样例:

3

首先题意转换:

1.带边权并查集

用 d[ x ] 表示 x 与 p[ x ]的关系:0表示同类,1表示不同类

#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_map>

using namespace std;
const int N=20010;

int n,m;
int p[N],d[N];
unordered_map<int,int> S;

int get(int x)
{
	if(S.count(x)==0)
	S[x]=++n;
	return S[x];
}

int find(int x)
{
	if(p[x]!=x)
	{
		int root=find(p[x]);
		d[x]+=d[p[x]];
		p[x]=root;
	}
	return p[x];
}

int main()
{
	cin>>n>>m;
	n=0; 
	for(int i=1;i<N;i++)
	p[i]=i;
	int res=m;
	for(int i=1;i<=m;i++)
	{
		int a,b;
		string type;
		cin>>a>>b>>type;
		a=get(a-1),b=get(b);
		int t=0;
		if(type=="odd")
		t=1;
		int pa=find(a),pb=find(b);
		if(pa==pb)
		{
			if(((d[a]+d[b])%2+2)%2!=t)
			{
				res=i-1;
				break;
			}
		}
		else
		{
			p[pa]=pb;
			d[pa]=d[a]^d[b]^t;
		}
	}
	cout<<res<<endl;
}

 2.拓展域

本题是分为奇数域和偶数域

#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
const int N=40010;
int n,m;
int p[N]/*,d[N]*/;
unordered_map<int,int> S;
int get(int x)
{
	if(S.count(x)==0)
	S[x]=++n;
	return S[x];
}
int find(int x)
{
	if(p[x]!=x)
	{
		/*int root=find(p[x]);
		d[x]+=d[p[x]];
		p[x]=root;*/
		p[x]=find(p[x]);
	}
	return p[x];
}
int main()
{
	cin>>n>>m;
	n=0; 
	for(int i=1;i<N;i++)
	p[i]=i;
	int res=m;
	for(int i=1;i<=m;i++)
	{
		int a,b;
		string type;
		cin>>a>>b>>type;
		a=get(a-1),b=get(b);
		/*int t=0;
		if(type=="odd")
		t=1;
		int pa=find(a),pb=find(b);
		if(pa==pb)
		{
			if(((d[a]+d[b])%2+2)%2!=t)
			{
				res=i-1;
				break;
			}
		}
		else
		{
			p[pa]=pb;
			d[pa]=d[a]^d[b]^t;
		}*/
		if(type=="even")
		{
			if(find(a+Base)==find(b))
			{
				res=i-1;
				break;
			}
			p[find(a)]=find(b);
			p[find(a+Base)]=find(b+Base);
		}
		else
		{
			if(find(a)==find(b))
			{
				res=i-1;
				break;
			} 
			p[(find(a+Base)]=find(b);
			p[find(a)]=find(b+Base);
		}
	}
	cout<<res<<endl;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值