本篇文章初衷在于整理练习思路,起到督促自己学习的作用,如能为你提供帮助,莫感荣幸。
前置芝士:【算法与数据结构】—— 并查集-优快云博客(不会并查集的先看或者自行寻找)
并查集的时间复杂度为 O(log* n) ,贴近于O(1)但是慢一些。
本文分成将三个模块
1.普及组 (洛谷黄题)
2.提高组 (洛谷绿题及以上)
3.省选组 (洛谷蓝题及以上)
写在前面:通篇刷过来,可以发现并查集代码短,并不复杂,真正实现起来代码非常灵活,重在思想,我已经很久没有直接写过merge函数了。
一、普及
纯板子结合一点基础知识(STL、结构体等等),过完下面这六道题相信你可以完全熟悉了并查集的模板,接下来就是实用了。
1.P3367 【模板】并查集
普通并查集(纯板子,不加解释)
传送门https://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 亲戚
传送门https://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 修复公路
传送门https://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 家谱
传送门https://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 村村通
传送门https://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 提高组] 奶酪
传送门https://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] 团伙
传送门https://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;
}
第二种
这种方法虽然暴力,但是也很巧妙。可以学习学习大佬的做法
传送门https://www.luogu.com.cn/article/h3b92kcb
2.P1621 集合
传送门https://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 【集合】https://www.luogu.com.cn/article/xr5uvbab
3.P1197 [JSOI2008] 星球大战
传送门https://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 提高组] 关押罪犯
传送门https://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;
}
其实这里我一开始想的是,一个贪心的做法,但是后面发现,我不能维护权值相等的情况。
此外,这里还提供一位佬的解法
传送门https://www.luogu.com.cn/article/not4ooq4
5.P1196 [NOI2002] 银河英雄传说
传送门https://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] 程序自动分析
传送门https://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 无线通讯网
传送门https://www.luogu.com.cn/problem/P1991
这道题很有迷惑性,而且要注意,只需要两个点之间距离小于某个数字就能一直走,而不是合距离(本人想了很久带权并查集,结果.....)
这题方法很多,如果用并查集的话可以考虑二分,对距离进行二分,找到一个最大距离,使连通块数量小于等于电话数量,因为一个电话可以串联一组连通块。
三、省选
1.P4185 [USACO18JAN] MooTube G
传送门https://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;
}
暂且收官!假如你一路跟过来,相信你已经能对并查集有了自己的理解。但是在不知道这个标签的时候能想出是并查集,这才是真正的搞懂了。
祝各位算法/工作/生活 一帆风顺!