[数据结构/并查集] 一篇文章带你通关并查集(题单)

本篇文章初衷在于整理练习思路,起到督促自己学习的作用,如能为你提供帮助,莫感荣幸。

前置芝士:【算法与数据结构】—— 并查集-CSDN博客(不会并查集的先看或者自行寻找)

并查集的时间复杂度为 O(log* n) ,贴近于O(1)但是慢一些。

本文分成将三个模块

1.普及组 (洛谷黄题)

2.提高组 (洛谷绿题及以上)

3.省选组 (洛谷蓝题及以上)

写在前面:通篇刷过来,可以发现并查集代码短,并不复杂,真正实现起来代码非常灵活,重在思想,我已经很久没有直接写过merge函数了

一、普及

纯板子结合一点基础知识(STL、结构体等等),过完下面这六道题相信你可以完全熟悉了并查集的模板,接下来就是实用了。

1.P3367 【模板】并查集

普通并查集(纯板子,不加解释)

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P3367

// Problem: 
//     P3367 【模板】并查集
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3367
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
using namespace std;
const int N=1e4+10;
int e[N];

int find(int x){
	if(x!=e[x]) e[x]=find(e[x]);
	return e[x];//返回根节点
}

void merge(int a,int b){
	int x=find(a);int y=find(b);
	//e[x]=y;连接到另外一个上,我看了看板子更加复杂,但是符合原理都能过
    这里前面加一个if(x!=y)会更好,若不然可能增加路径压缩的次数
}

int main(){
	int n,m;cin>>n>>m;
	for(int i=1;i<=n;++i) e[i]=i;
	while(m--){
		int a,b,c;cin>>a>>b>>c;
		if(a==1){
			merge(b,c);
		}
		else{
			int k1=find(b);int k2=find(c);
			if(k1==k2) cout<<"Y"<<endl;
			else cout<<"N"<<endl;
		}
	}
	return 0;
}

2.P1551 亲戚

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1551

一道算是应用场景的题目,可以拿来熟熟手

// Problem: 
//     P1551 亲戚
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1551
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
using namespace std;
const int N=5005;
int fa[N];

int find(int x){
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];//返回根节点
}

void merge(int a,int b){
	int x=find(a);int y=find(b);
	fa[x]=y;
}


int main(){
	int n,m,p;cin>>n>>m>>p;
	for(int i=1;i<=n;++i) fa[i]=i;
	while(m--){
		int a,b;cin>>a>>b;
		merge(a,b);
	}
	while(p--){
		int a,b;cin>>a>>b;
		int x=find(a),y=find(b);
		if(x==y) cout<<"Yes"<<endl;
		else cout<<"No"<<endl;
	}
	
	
	return 0;
}

3.P1111 修复公路

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1111

五花八门的答案,结合了一点排序知识

本人用了结构体排序后全遍历,O(nm)的时间复杂度(1e8)擦边过了

// Problem: 
//     P1111 修复公路
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1111
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e3+10;
int fa[N];
struct node{
	int a,b,c;
	bool operator < (const node &p)const {return c<p.c;};//重载
}nodes[100005];

int find(int x){
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}

void merge(int x,int y){
	int a=find(x),b=find(y);
	if(a!=b) fa[a]=b;//还是这么写吧,时间更优一点
}

int main(){
	int n,m;cin>>n>>m;
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<=m;++i){
		cin>>nodes[i].a>>nodes[i].b>>nodes[i].c;
	}
	sort(nodes+1,nodes+1+m);
	for(int i=1;i<=m;++i){
	    merge(nodes[i].a,nodes[i].b);
	    bool check=true;
	    for(int i=2;i<=n;++i){
	        if(find(i)!=find(i-1)){
	        //这里注意合并完没有merge的fa是没有直接更新的,不能直接比较fa[i]
	        	 check=false;break;
	        }
	    }
	    if(check){
	    	 cout<<nodes[i].c<<endl;return 0;
	    }
	}
	cout<<-1<<endl;
	return 0;
}

这个地方有更优的方法O(m),遍历一遍,如果遇到根节点不同,连通块数量减一并合并,这样连通块数量等于1的时候,输出答案就好了(只需要操作n-1次)

4.P2814 家谱

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P2814

这道题我个人结合了map,调到吐血,越改越复杂,细节还是蛮多的(不熟练

#include<iostream>
#include<map>
using namespace std;
const int N=5e4+10;
int fa[N];
map<string,int> mp;//记录每个点的标记
map<int,string> mpp;//在最后返回根节点的字符串
int cnt;

int find(int x){
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}

void merge(int f,int a){
	int x=find(f),y=find(a);
	if(x!=y) fa[y]=x;
}

int main(){
	string s;
	int now;
	while(cin>>s){
		string k=s.substr(1);
		if(s=="$") break;
		else if(s[0]=='#'){
			if(!mp[k]){
				 mp[k]=++cnt;
				 fa[cnt]=cnt;
				 now=cnt;
				 mpp[cnt]=k;
			}
			else now=mp[k]; 
		}
		else if(s[0]=='+'){
			if(!mp[k]){
				 mp[k]=++cnt;
				 fa[cnt]=cnt;
				 mpp[cnt]=k;
			}
			merge(now,mp[k]);
		}
		else{
			int t=find(mp[k]);
//这个地方WA了n发,因为最后步find,这里还保留的是上一次的father,要find一次才能更新
			cout<<k<<' '<<mpp[t]<<endl;
		}
	}	
	return 0;
}

除了我的代码外,这里奉上大佬的一个题解,一样的思路,但是代码简单,炉火纯青。

(我被板子局限了思路,确实没想到路径压缩可以直接压缩map)

5.P1536 村村通

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1536

这道题如果把前面的吃透了,想起本文第三题结尾大佬的一个办法,计算连通块数量,如果find查找的根节点不同,就连通块数量-1并合并,连通块开始初始化为n-1。

// Problem: 
//     P1536 村村通
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1536
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
using namespace std;
const int N=1005;
int fa[N];

int find(int x){
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}

void merge(int a,int b){
	int x=find(a),y=find(b);
	if(x!=y) fa[x]=y;
}

int main(){
    int a,b;
    while(cin>>a){
    	if(a==0) break;
    	for(int i=1;i<=a;++i) fa[i]=i;
    	cin>>b;
    	int ans=a-1;
    	while(b--){
    		int x,y;cin>>x>>y;
    		if(find(x)!=find(y)) ans--,merge(x,y);
    	}
    	if(ans>=0) cout<<ans<<endl;
    	else cout<<0<<endl;
    }	
	return 0;
}

这种方法的初衷其实更适合实时在某一刻卡断一类的,就是说能知道各时刻连通块的数量,这道题其实暴力就可以解决掉(因为给的全部都要操作)

6.P3958 [NOIP2017 提高组] 奶酪

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P3958非常伤心,这道题卡了我两个钟,最终发现原因(h的范围是1e9,不能直接暴力,写假了)

其实就是一段算成一个点,合并集合,最后把上下两个面再维护一下,看看上下两个面在不在一个集合里,可以配合这篇:远古大神 食用~

// Problem: 
//     P3958 [NOIP2017 提高组] 奶酪
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3958
// Memory Limit: 250 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N=1003;
#define int long long
int fa[N];
int n,h,r;
struct node{
	int a,b,c;
}nodes[N];
bool check(node x,node y){
	int num=(x.a-y.a)*(x.a-y.a)+(x.b-y.b)*(x.b-y.b)+(x.c-y.c)*(x.c-y.c);
	if(num>(2*r*r*2)) return false;
	return true;
}
int find(int x){
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
void merge(int a,int b){
	int x=find(a),y=find(b);
	if(x!=y) fa[y]=x;//高的往低处接
}
signed main(){
	int t;cin>>t;
	while(t--){
		cin>>n>>h>>r;
		for(int i=1;i<=n+2;++i) fa[i]=i;
		for(int i=1;i<=n;++i){
		    cin>>nodes[i].a>>nodes[i].b>>nodes[i].c;
		}
		for(int i=1;i<=n;++i){
			for(int j=i+1;j<=n;++j){
			if(check(nodes[i],nodes[j])){
				merge(i,j);
			}
		}}
		for(int i=1;i<=n;++i){
			if(nodes[i].c-r<=0) merge(i,n+1);
			if(nodes[i].c+r>=h) merge(i,n+2);//看看能不能串起来
		}
		if(find(n+1)==find(n+2)) cout<<"Yes"<<endl;
		else cout<<"No"<<endl;
	}
	return 0;
}

二、提高

可以发现,从绿题开始,并查集开始融入一些思维难度和其他算法知识点

1.P1892 [BOI2003] 团伙

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1892这道题在一开始我考虑的是权值并查集的写法,最后发现写假了。题目有几个小细节需要注意(我被误导了反正),这道题其实就是计算有几个团体,不要被最多所欺骗。

这里提供两种做法

第一种

我也是看了这题后才知道反集的概念(可以看链接详情)

简单来说就是让你维护两个性质相反的集合,就多开n的空间,大于n的空间称作反集

举例来讲

如果a和b是敌人,合并n+b和a,n+a和b
如果a和c是敌人,合并n+c和a,n+a和c
这样一来与那个反集所在一个集合的就都是朋友了,因为敌人的敌人是朋友
模拟一下还是比较好理解的
// Problem: 
//     P1892 [BOI2003] 团伙
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1892
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<cmath>
using namespace std;
const int N=2010;
int fa[N];
int n,m;
void init(){
	for(int i=1;i<=n*2;++i) fa[i]=i;//开两倍,后一半是反集
}
int find(int x){
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
int main(){
	cin>>n>>m;
	init();
	while(m--){
		char a;int x,y;
		cin>>a>>x>>y;
		if(a=='F'){
			fa[find(x)]=fa[find(y)];//直接合并就好了
		}
		else{
			fa[find(x+n)]=fa[find(y)];
        //这里必须是这个合并顺序,因为后面要对前n各进行查询,父节点必须在前n个
			fa[find(y+n)]=fa[find(x)];
		}
	}
	int ans=0;
	for(int i=1;i<=n;++i) if(fa[i]==i) ans++;//如果这个是根节点,就多一个团体
	cout<<ans<<endl;
	return 0;
}

第二种

这种方法虽然暴力,但是也很巧妙。可以学习学习大佬的做法

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/article/h3b92kcb

2.P1621 集合

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1621明白原理后实现难度并不大(当然我没想到),这里运用了一点数学知识(质数筛),思想的精髓在于取质数,然后让质数的倍数符合条件的成为一个集合,可以证明这种方法有效

// Problem: 
//     P1621 集合
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1621
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)
#include<iostream>
using namespace std;
const int N=1e5+10;
bool nums[N];
long long f[N],cot=0;
int n,m,k;
int fa[N];
int find(int x){
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
void merge(int a,int b){
	int x=find(a),y=find(b);
	if(x!=y) fa[x]=y;
}
void prime(){
	nums[1]=true;//1不是素数 true不是素数
    for(long long i=2;i<=m;i++)
    {
        if(!nums[i]) f[cot++]=i;//我是从0开始的,要注意2是f[0]
        for(long long j=0;j<cot&&i*f[j]<=m;j++)//这里开多大上面N就要开多大
        {
            nums[f[j]*i]=true;
            if(i%f[j]==0) break;
        }
    }
}
 
int main(){
    cin>>n>>m>>k;
    for(int i=1;i<=m;++i) fa[i]=i;
    prime();
    for(int i=0;i<cot;++i){
        if(f[i]<k) continue;
        int t=(n+f[i]-1)/f[i];
//让质数乘t大于等于n,求这个t,+f[i]上取整,-1防止整除情况,这个是我第二次遇见了,之前在一道cf的c题碰上过
    	for(int j=t*f[i];j<=m;j+=f[i]){
    		merge(t*f[i],j);//一定要融合后面的
    	}
    }
    int ans=0;
    for(int i=n;i<=m;++i){
    	if(fa[i]==i) ans++;//连通块
    }
    cout<<ans<<endl;
	return 0;
}

除此之外,这里同样给出一个暴力解法(不用上面那个上取整公式的),这里贴上博客自取

题解 P1621 【集合】icon-default.png?t=N7T8https://www.luogu.com.cn/article/xr5uvbab

3.P1197 [JSOI2008] 星球大战

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1197

一道思维并查集,写的时候想到了正解思路(倒着维护),损坏不好处理,我们就可以考虑倒着创造(codeforces某场div2的C题也是这个思路),我debug了一下午,伤心告负。

总结原因,我认为是我起初的方法倒着维护的时候不能有效处理非存在(已经被炸掉的)的连接问题,传统的并查集是不能解决的。在这个变种中,我们应当考虑用图来维护(因为连接的路息息相关,不只是简单的维护区间了)

话不多说,贴代码(有注释)

// Problem: 
//     P1197 [JSOI2008] 星球大战
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1197
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<vector>
using namespace std;
int n,m,k;
const int N=4e5+10;
int fa[N];
vector<int> e[N];
int killed[N];//标记为1说明现在还是毁灭状态
int order[N];//记录被干掉的顺序
int cnt[N];//记录输出
int ans;
void init(){
	for(int i=0;i<n;++i) fa[i]=i;
}
int find(int x){
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x]; 
}
void solve(){//邻接表
	int a,b;cin>>a>>b;
	e[a].push_back(b);
	e[b].push_back(a);
}
void dfs(int now){
	for(unsigned int i=0;i<e[now].size();++i){
		if(killed[e[now][i]]) continue;//还没被修复----这里就弥补了我上面所没操作到的地方
		int a=find(now),b=find(e[now][i]);
		if(a==b) continue;
//这里可以直接跳过了,我原本还在疑惑会不会这个连上它后面的没连上,后来发现不会,因为肯定是因为当时没被修复
        ans--;
        fa[a]=b;
        dfs(e[now][i]);
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;++i) solve();
	init();cin>>k;
	for(int i=1;i<=k;++i) cin>>order[i],killed[order[i]]=1;
	ans=n-k;
	for(int i=0;i<n;++i){
		if(killed[i]) continue;
		dfs(i);
	}
	cnt[k+1]=ans;
	for(int i=k;i>=1;--i){//修复
	    ans++;
	    killed[order[i]]=0;
	    dfs(order[i]);
	    cnt[i]=ans;
	}
	for(int i=1;i<=k+1;++i) cout<<cnt[i]<<endl;
	return 0;
}

明显发现,这里与简单图论相结合(我不咋会)。越高难度的题目越考研对多种简单算法的熟悉与应用能力,以期达到融会贯通的状态

4.P1525 [NOIP2010 提高组] 关押罪犯

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1525

分到两座监狱,咦?,突然发现,这似乎与P1892 [BOI2003] 团伙中的反集有异曲同工之妙啊(我早该想到的)!从最大的开始,尽可能的把两个分开,首先,对于同一个,如果同时有两个都可以和他分开的,这两个就在一个集合中(敌人的敌人是朋友)。再者,对于已经在同一集合中的,那就只好让他们打架了(bushi

理论可行,实践开始!

// Problem: 
//     P1525 [NOIP2010 提高组] 关押罪犯
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1525
// Memory Limit: 125 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
#define endl '\n'
const int N=4e5+10;
int fa[N];
int n,m;
struct node{
	int x,y,z;
	bool operator < (const node &t) const{return z>t.z;};
}nodes[N];
int find(int x){
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
void init(){
	for(int i=1;i<=n*2;++i) fa[i]=i;//两倍,后面是反集
}
int main(){
    cin>>n>>m;	
	for(int i=1;i<=m;++i){
		cin>>nodes[i].x>>nodes[i].y>>nodes[i].z;
	}
	sort(nodes+1,nodes+m+1);
	init();
	if(n==2){//在这里要注意2是特殊情况,其实1也是,大于3就必须要打架了
		cout<<0<<endl;return 0;
	}
	for(int i=1;i<=m;++i){
		if(find(nodes[i].x)==find(nodes[i].y)){
			cout<<nodes[i].z<<endl;break;
		}
		else{
			fa[find(nodes[i].x+n)]=fa[find(nodes[i].y)];//自己写merge的方向
			fa[find(nodes[i].y+n)]=fa[find(nodes[i].x)];
            //后面这两个把fa[]去掉也行,其实我一直不知道为啥要写那个框(方便cv
            //记得要find 因为我们是一直在1-n之间合并的
		}
	}
	return 0;
}

其实这里我一开始想的是,一个贪心的做法,但是后面发现,我不能维护权值相等的情况。

此外,这里还提供一位佬的解法

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/article/not4ooq4

5.P1196 [NOI2002] 银河英雄传说

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1196

最喜欢的带权并查集,想写这个很久了!我debug了很久但是楞是过不了,看了题解发现只有一个权值处理的不同(我写的可能反复维护导致错误了吧),但是思路大致都是相同的。于是这里贴出的是优化版本

// Problem: 
//     P1196 [NOI2002] 银河英雄传说
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1196
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<cmath>
using namespace std;
const int N=3e4+10;
int d[N];
int fa[N];
int cnt[N];//记录个数,就是这里与他们不一样
void init(){
	for(int i=1;i<=30000;++i) fa[i]=i,cnt[i]=1;
}
int find(int x){//滚瓜烂熟,建议大家找一个板子理解之后直接背下来
	if(fa[x]!=x){
		int t=fa[x];
		fa[x]=find(fa[x]);
		d[x]+=d[t];
	}
	return fa[x];
}
int main(){
	int n;cin>>n;
	init();
    while(n--){
    	char a;int b,c;
    	cin>>a>>b>>c;
    	int x=find(b),y=find(c);
    	if(a=='M'){
    		fa[x]=y;
    		d[x]=cnt[y];
    		cnt[y]+=cnt[x];。。不要写反,因为这个浪费一发WA
    		cnt[x]=0;
    	}
    	else{
    		if(x==y){
    			cout<<abs(d[b]-d[c])-1<<endl;//显而易见
    		}
    		else{
    			cout<<-1<<endl;
    		}
    	}
    }	
	return 0;
}

写过食物链这道题后,就发现这道题好想很多。

噢对了,这里还有一个双倍经验,但是蓝题难度(基本一样,上一题懂原理这里肯定没问题

作为一道蓝题,代码量是真的少(code is cheap,后面的忘了

代码如下

5.1 P5092 [USACO04OPEN] Cube Stacking

// Problem: 
//     P1196 [NOI2002] 银河英雄传说
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1196
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<cmath>
using namespace std;
const int N=3e4+10;
int d[N];
int fa[N];
int cnt[N];//记录个数
void init(){
	for(int i=1;i<=30000;++i) fa[i]=i,cnt[i]=1;
}
int find(int x){
	if(fa[x]!=x){
		int t=fa[x];
		fa[x]=find(fa[x]);
		d[x]+=d[t];
	}
	return fa[x];
}
int main(){
	int n;cin>>n;
	init();
    while(n--){
    	char a;int b,c;
    	cin>>a>>b;
    	if(a=='M'){
    		cin>>c;
    		int x=find(b),y=find(c);
    		fa[x]=y;
    		d[x]=cnt[y];
    		cnt[y]+=cnt[x];
    		cnt[x]=0;
    	}
    	else{	
    	    int k=find(b);//这里是一个糟点,记得要用find更新一下状态
    		cout<<d[b]<<endl;//这里只要它前面的全部
    	}
    }	
	return 0;
}

6.P1955 [NOI2015] 程序自动分析

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1955

原本一开始我想的是用反集来处理一下,结果写了大半天,发现根本没必要。直接离线一下,先维护1再维护0,0和1不能在一个区间里面。考虑数据范围1e9,用离散化。这里我先用map试了一发爆空间了,1e9的数据量,要用900多MB,这里明显不行。

于是,只好采用离散化的方法来处理

不会离散化的可以参考这篇

// Problem: 
//     P1955 [NOI2015] 程序自动分析
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1955
// Memory Limit: 500 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define endl '\n'
int _;
const int N=1e5+10;
int fa[N*2];
struct node{
	int x,y,z;
	bool operator<(const node&t) const{return z>t.z;};
}nodes[N];
int old[N*2];//放下所有数字

int find(int x){
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}

void solve(){
    int n;cin>>n;
    int num=0;
    for(int i=1;i<=n*2;++i) fa[i]=i;
    for(int i=1;i<=n;++i){
    	cin>>nodes[i].x>>nodes[i].y>>nodes[i].z;
    }
    sort(nodes+1,nodes+1+n);
    for(int i=1;i<=n;++i){
    	old[++num]=nodes[i].x;old[++num]=nodes[i].y;
    }
    sort(old+1,old+1+num);
    int cnt=unique(old+1,old+1+num)-(old+1);
    for(int i=1;i<=n;++i){//因为我已经排序完了,所以一遍处理一遍就可以找关系了
    	nodes[i].x=lower_bound(old+1,old+1+cnt,nodes[i].x)-old;
    	nodes[i].y=lower_bound(old+1,old+1+cnt,nodes[i].y)-old;
    	int a=find(nodes[i].x),b=find(nodes[i].y);
    	if(nodes[i].z==1){
    		if(a!=b) fa[a]=b;
    	}
    	else{
    		if(a==b){
    			cout<<"NO"<<endl;
    			return;
    		}
    	}
    }
    cout<<"YES"<<endl;
}

signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}    

7.P1991 无线通讯网

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P1991

这道题很有迷惑性,而且要注意,只需要两个点之间距离小于某个数字就能一直走,而不是合距离(本人想了很久带权并查集,结果.....)

这题方法很多,如果用并查集的话可以考虑二分,对距离进行二分,找到一个最大距离,使连通块数量小于等于电话数量,因为一个电话可以串联一组连通块。

代码可以见这里

三、省选

1.P4185 [USACO18JAN] MooTube G

传送门icon-default.png?t=N7T8https://www.luogu.com.cn/problem/P4185

怎么说呢,没有结合其他算法,这里主要还是考察思想和对这类题型的熟悉程度。因为读假题我想了好久.......

这里使用了离线处理的思想,由于对某一个查询,只能留存比它大的,我们可以排序一下离线处理从大的开始,然后用一个while,讲大于等于这个k的所有连边全部合并,最后连通块的大小-1,就是我们每一次查询的答案,每一次查询之后,后一次因为更小,前面那些更大的数据一定满足。

贴代码

#include<iostream>
#include<algorithm>
using namespace std;
#define endl '\n'
const int N=1e5+10;
int fa[N],ans[N],res[N];//父亲是谁,这一次查询的离线情况,合并总个数
struct node{
	int a,b,c;//n-1条边
	bool operator<(const node&t)const{return c>t.c;};
}nodes[N];
struct q{
	int k,w,id;//大于k和哪条边,编号
	bool operator<(const q&t)const{return k>t.k;};
}query[N];
int find(int x){
	if(fa[x]!=x) fa[x]=find(fa[x]);
	return fa[x];
}
int n,m;
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<n;++i) cin>>nodes[i].a>>nodes[i].b>>nodes[i].c;
	for(int i=1;i<=m;++i) cin>>query[i].k>>query[i].w,query[i].id=i;
	sort(nodes+1,nodes+n);
	sort(query+1,query+1+m);
	for(int i=1;i<=n;++i) fa[i]=i,res[i]=1;
	int now=1;//记录现在到什么位置了
	for(int i=1;i<=m;++i){
		while(now<n&&nodes[now].c>=query[i].k){
			int x=find(nodes[now].a),y=find(nodes[now].b);
			if(x==y) continue;
			fa[x]=y;
			res[y]+=res[x];
			now++;
		}
		ans[query[i].id]=res[find(query[i].w)]-1;
	}
	for(int i=1;i<=m;++i) cout<<ans[i]<<endl;
	return 0;
}

暂且收官!假如你一路跟过来,相信你已经能对并查集有了自己的理解。但是在不知道这个标签的时候能想出是并查集,这才是真正的搞懂了。

祝各位算法/工作/生活 一帆风顺!

  • 87
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值