CF1961 E Number of Groups(并查集)

题面:https://codeforces.com/contest/1691/problem/E

这道题真的几把坑。

解题思路:

有几组连在一起,所以很显然会想到并查集。

假设我们现在先不看颜色,那很自然的人类的想法, 就是从左到右找有没有断点。

这样可以判断就几组,但是无法判断连的关系,所以思考连在一起的关系,就是

  • a.l<=b.l
  • a.r>=b.l

所有我们只要给2n个点(左端点和右端点)拍个序,扫过去,如果是左端点就存进来,如果是右端点就将对应的组删去。假如我们走到pos点且这个点是左端点,这时候有5个组还在里面,那个这个组和这5个组就是相连的。(理解一下上面两个式子这就很自然了)

不过注意,排序的时候因为点可能重合,所有如果点的pos都一样,得先放左端点。

理解了连的关系,接下来看怎么连,如果和每个都连,那就是n2,所以得用并查集连他们的爹。

哪个能代表这5个组的爹呢,我们要的爹是可以代表这么多个点,而且考虑这5个点+新进来的点有消失/都是没消失的情况下,又新进来个左端点, 那么这时候左端点就是只要连这个爹。(假设在5个点之前,我们还没有采用连爹的策略,这一步我们聪明了)没消失,连那个都行;如果消失了一个,那是不是连4+1都行,如果消失了两个,那只能连3+1……所以很简单,爹得保证儿子全死了,自己还可以被连,所以就是最晚死的那个,也就是右端点最远的那个组。

现在我们考虑颜色,还是刚才的5个的情况(这5个同色),假设来个不同颜色的,我就得连这5个,接下来再来一个不同颜色的, 那我就只需要连5个里面右端点最远的那个。

这时候就有个问题,为啥第一个得连5个,第二个只用连一个。

实际上是我们知道了5是爹,所以第二个只要连他就行。所以怎么知道5是爹?很简单,把他儿子全sha了就行。

大致过程就是这样,维护两个有序数组表示两个颜色。

有一个无敌坑的点:如果你打算用set做的话,结构体重载的时候,记得把结构体所有的东西最好都比比,即使他没啥意义,不然就会出现erase({2,3}),但是把{2,4}给删了 (set查你要找的东西,是a不大于b,b不大于a,就认为他们相等)

(因为思路改了几次,所以定义的很乱,见谅)

代码实现:

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
#define DEBUG(x) cout<<"** "<<x<<" **"<<endl;
using namespace std;
typedef long long ll;
struct node{
	int pos;
	int color;
	int lr;
	int num;
};
struct point{
	int color;
	int l;
	int r;
}pp[200005];
struct Right{
	int num;
	int r;
	friend bool operator < (Right a,Right b){
		if(a.r!=b.r)return a.r>b.r;
		else return a.num<b.num;
	}
};
int n;
bool cmp(node a,node b){
	if(a.pos!=b.pos){
		return a.pos<b.pos;
	}else{
		return a.lr<b.lr;
	}
}
int fa[200005];
//int vis[200005];
void init(){
	for(int i=1;i<=n;i++){
		fa[i]=i;
//		vis[i]=0;
	}
}
int find(int a){
	if(fa[a]==a){
		return a;
	}
	return fa[a]=find(fa[a]);
}
void connect(int a,int b){
	a=find(a);
	b=find(b);
	if(a!=b){
		fa[a]=b;
	}
}
void dd(){
	for(int i=1;i<=n;i++){
		printf("%d $\t",find(i));
	}
	printf("\n");
}
int main(){
	int t;
	cin>>t;
	int a,b,c;
	while(t--){
		cin>>n;
		vector<node> p;
		for(int i=1;i<=n;i++){
			scanf("%d %d %d",&a,&b,&c);
			p.push_back({b,a,0,i});
			p.push_back({c,a,1,i});
			pp[i]={a,b,c};	
		}
		init();
//		dd();
		sort(p.begin(),p.end(),cmp);
		set<Right> st0;
		set<Right> st1;
		for(node tmp:p){
			if(tmp.lr==1){
				if(tmp.color==0){
					st0.erase({tmp.num,tmp.pos});
				}else{
					st1.erase({tmp.num,tmp.pos});
				}
				continue;
			}
			if(tmp.color==0){
				st0.insert({tmp.num,pp[tmp.num].r});
				if(!st1.empty()){
					Right ttt=(*st1.begin());
					for(auto tt:st1){
						connect(tt.num,tmp.num);
					}
					st1.clear();
					st1.insert(ttt);
				}
			}else{
				st1.insert({tmp.num,pp[tmp.num].r});
				if(!st0.empty()){
					Right ttt=(*st0.begin());
					for(auto tt:st0){
						connect(tt.num,tmp.num);
					}
					st0.clear();
					st0.insert(ttt);
				}
			}
//			dd();
		}
		set<int> c;
		for(int i=1;i<=n;i++){
			c.insert(find(i));
		}
		cout<<c.size()<<endl;
	}	
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值