并查集相关

板子题 - 亲戚

在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 5005;
int fa[N] = {0},dep[N] = {0};//rank ambiguous了 
int n,m,p,x,y;

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

void add(int u,int v){
	int fu = find(u),fv = find(v);
	if(fu==fv) return ;//fu和fv是已经找到了唯一的根,因为在find里面,终止条件规定了必须fa[u]=u,一个 
	if(dep[fu]<dep[fv]) fa[fu] = fv;
	if(dep[fu]>dep[fv]) fa[fv] = fu;
	else{
		fa[fu] = fv;
		dep[fv]++;
	} 
	return ;
}

void query(int u,int v){
	int fu = find(u),fv = find(v);//必须写find,有可能刚合并完只有祖先过去了,别的结点还没找着 
	if(fu==fv) cout<<"Yes"<<endl;
	else cout<<"No"<<endl;
	return ;
}

int main(){
	cin>>n>>m>>p;
	for(int i=1;i<=n;i++)
		fa[i] = i;
	for(int i=1;i<=m;i++){
		cin>>x>>y;
		add(x,y);//不可以写fa[x] = find(y),必须写fa[find(x)]=find(y),否则x所在的一整棵树只有x一个人到了y集合里…… 
	}
	for(int i=1;i<=p;i++){
		cin>>x>>y;
		query(x,y);
	}
	return 0; 
} 

  • 路径压缩和按秩合并一起用,效率达到最高
    路径压缩保证经过一次路径遍历的结果都被记忆化;按秩合并保证两个树合并后再对其一进行路径遍历用时最短
  • 其他的tips写在代码里了,主要是要写熟练,每一步究竟应该怎么办都应该想清楚,特别是什么时候要用find函数而不是仅仅一个简单的fa[x]

几乎板子题 - 奶酪

在这里插入图片描述

#include<iostream>
#include<cmath> 
using namespace std;
const int N = 1005;
int T,n,h,r;
long long x[N],y[N],z[N];//n+1底z=0,n+2顶z=h 
int fa[N],dep[N];

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

void Union(int u,int v){
	int fu = find(u),fv = find(v);
	if(fu==fv) return ;
	if(dep[fu]<dep[fv]) fa[fu] = fv;
	if(dep[fu]>dep[fv]) fa[fv] = fu;
	if(dep[fu]==dep[fv]){
		fa[fu] = fv;
		dep[fv]++;
	}
}

void check(int u,int v){
	if(v==n+1){//底 
		if(z[u]-r<=0 && z[u]+r>=0) Union(u,v);
		return ;//忘记return可不行……会导致后面再算一遍dis,由于初始化的时候n+1和n+2都没初始化,导致算出来dis是x^2+y^2+z^2,说不准就小于2r了 
	}
	if(v==n+2){
		if(z[u]+r>=h && z[u]-r<=h) Union(u,v);
		return ;
	}
	double dis = sqrt((x[u]-x[v])*(x[u]-x[v])+(y[u]-y[v])*(y[u]-y[v])+(z[u]-z[v])*(z[u]-z[v]));
	if(dis <= 1.0*2*r) Union(u,v);
	return ;
}

void query(int u,int v){
	int fu = find(u),fv = find(v);
	if(fu==fv){
		printf("Yes\n");
		return ;
	}
	printf("No\n");
	return ;
} 

int main(){
	scanf("%d",&T);
	for(int i=1;i<=T;i++){
		scanf("%d%d%d",&n,&h,&r);
		for(int i=1;i<=n+2;i++){
			fa[i] = i;
			dep[i] = 0;
		}
		for(int i=1;i<=n;i++)
			scanf("%d%d%d",&x[i],&y[i],&z[i]);
		for(int i=1;i<=n;i++){
			for(int j=i+1;j<=n+2;j++)
				check(i,j);
		} 
		query(n+1,n+2);
	} 
	return 0;
}

注意特判上底面和下底面就好了

关押罪犯 - enemy

在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 20005;
const int M = 100005;
int n,m;
struct Event{
	int a,b;
	int c;
	Event &operator =(Event &x){
		if(this == &x) return *this;
		a = x.a;
		b = x.b;
		c = x.c;
		return *this;
	} 
}event[M];
int fa[N] = {0},enemy[N] = {0};

int divide(int low,int high){
	Event k = event[low];
	while(low!=high){
		while(low<high && event[high].c <= k.c) high--;
		if(low<high) {
			event[low] = event[high];
			low++;//确定了这时候low的值一定是大于k 
		}
		while(low<high && event[low].c >= k.c) low++;
		if(low<high) {
			event[high] = event[low];
			high--;
		}
	}
	event[low] = k;
	return low;
}

void quicksort(int low,int high){
	if(low>=high) return ;
	int mid = divide(low,high);
	quicksort(low,mid-1);
	quicksort(mid+1,high);
	return ;
}

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

bool solve(int x,int y){
	int fx = find(x),fy = find(y);
	if(fx == fy) return false;
	if(enemy[fx]==0) enemy[fx] = fy;
	else fa[fy] = find(enemy[fx]);
	if(enemy[fy]==0) enemy[fy] = fx;
	else fa[fx] = find(enemy[fy]);
	return true;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		fa[i] = i;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&event[i].a,&event[i].b,&event[i].c);
	}
	quicksort(1,m);
	for(int i=1;i<=m;i++){
		if(!solve(event[i].a,event[i].b)){
			printf("%d\n",event[i].c);
			return 0; 
		}
	}
	printf("0\n");
	return 0;
} 

quicksort写的我很痛苦……因为一不小心把while写成if了,导致只有10分……而且quicksort名堂还挺多的,比如说写法很多种,写的时候语句顺序还要相应地调整好才行,下面放几个案例(都不同程度地TLE了,取mid的那个还MLE了不知道为什么……因为我没有选择用随机数,随机数才是比较合理的策略)

int divide(int low,int high){
	int k = a[low];//这里写low,下面的while循环里面先动high,否则不对
	while(low!=high){//呃呃呃过程要背吧……现推应该来不及的吧
		while(low<high && a[high]>=k) high--;
		if(low<high){
			a[low] = a[high];
			low++;
		}
		while(low<high && a[low]<=k) low++;
		if(low<high){
			a[high] = a[low];
			high--;
		}
	}
	a[low] = k;//不exchange的话就要最后赋一下值,exchange的话我也不知道,因为我是在没时间研究了,下面有机会再研究吧
	return low;
}

void quicksort(int low,int high){
	if(low>=high) return ;
	int mid = divide(low,high);
	quicksort(low,mid-1);
	quicksort(mid+1,high);
	return ;
}
int divide(int low,int high){
	int k = a[(low+high)/2]; 
	while(low<=high){
		while(low<high && a[low]<k) low++;
		while(low<high && a[high]>k) high--;
		if(low<=high){
			swap(a[low],a[high]);
			low++;
			high--;
		}
	}
	return high;
}

void quicksort(int low,int high){
	if(low>=high) return ;
	int mid = divide(low,high);
	quicksort(low,mid-1);
	quicksort(mid+1,high);
	return ;
}

还有的写法是左右指针都动,动完exchange,但是边界条件和上面这种不一样,细节处理也不一样……有兴趣再研究吧,我已经写不动了。

食物链 - 带权并查集

在这里插入图片描述

这题从很久以前就开始整我,到现在还在整我,主要原因是我确实没搞太懂,带权并查集的权值递推关系需要列表找规律,规律很难找,有时候要配合对权值的理解来做,比如说这题的食物链是一个三元闭合的环,它的权值可以理解为关系的递进,也就是长度,只是因为是三元的环所以每次还要对3取模。

#include<iostream>
using namespace std;
const int N = 50005;
int n,k,x,y,op,ans = 0;
int fa[N],w[N] = {0};

int find(int x){
	if(fa[x]==x) return x;
	int ffx = find(fa[x]);
	w[x] = (w[fa[x]]+w[x])%3; //此时fa[fa[x]]=ffx, w也是捋好了的 
	fa[x] = ffx;
	return fa[x];
}

bool check(int op,int x,int y){
	int fx = find(x),fy = find(y);
	if(fx==fy){//有关系 
		if(op==1){//同类 
			if((w[y]-w[x]+3)%3==0) return true;
			else return false; 
		}
		if(op==2){//x吃y,w[y]比w[x]大1,y走w[y]到父亲,x走w[x]到父亲,x在y,上方w[y]大于w[x],有点像路径长度,只是说循环路径所以对3取模 
			if((w[y]-w[x]+3)%3==1) return true;
			else return false; 
		}
	} 
	//无关系,建立关系
	fa[fy] = fx;//x吃y,x在上面 
	if(op==1){
		w[fy] = (w[x]-w[y]+3)%3;//等号后面都是x和y啦,不是fx fy,是得出的结论的规律而已! 
	} 
	if(op==2){
		w[fy] = (w[x]-w[y]+1+3)%3;
	}
	return true;
} 

int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		fa[i] = i;
	}
	for(int i=1;i<=k;i++){//第一种说法是 1 X Y,表示 XY是同类。第二种说法是2 X Y,表示 X吃Y。
		scanf("%d%d%d",&op,&x,&y); 
		if(x>n || y>n ||(op==2 && x==y)) {
			ans++;
			continue;
		}
		if(!check(op,x,y)){
			ans++;
		}
	}
	cout<<ans<<endl;
	return 0;
}
/*
    当前的话与前面的某些真的话冲突,就是假话
    当前的话中 X 或 Y 比 N 大,就是假话
    当前的话表示 X 吃 X,就是假话
*/

写的时候忘记仔细读题了,假话的判别不止一个点;
写的时候还是在权值的递推上面出了问题,具体体现在注释的地方,到底写什么参数,写加号还是减号,谁减谁,都是需要好好考虑的问题。
下面再放一个改进过的代码:

#include<iostream>
using namespace std;
const int N = 50005;
int n,k,x,y,op,ans = 0;
int fa[N],w[N] = {0};

int find(int x){
	if(fa[x]==x) return x;
	int ffx = find(fa[x]);
	w[x] = (w[fa[x]]+w[x])%3; //此时fa[fa[x]]=ffx, w也是捋好了的 
	fa[x] = ffx;
	return fa[x];
}

bool check(int op,int x,int y){
	int fx = find(x),fy = find(y);
	if(fx==fy){//有关系 
		if((w[y]-w[x]+3)%3==op) return true;
		else return false; 
	} 
	//无关系,建立关系
	fa[fy] = fx;//x吃y,x在上面 
	w[fy] = (w[x]-w[y]+op+3)%3;//这里求的是fy到fx的距离,也就是二者之间的关系,因为合并的时候设fa[fy] = fx了
	return true;
} 

int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		fa[i] = i;
	}
	for(int i=1;i<=k;i++){//第一种说法是 1 X Y,表示 XY是同类。第二种说法是2 X Y,表示 X吃Y。
		scanf("%d%d%d",&op,&x,&y); 
		if(x>n || y>n ||(op==2 && x==y)) {
			ans++;
			continue;
		}
		if(!check(op-1,x,y)){
			ans++;
		}
	}
	cout<<ans<<endl;
	return 0;
}

具体改进体现在check函数更加智能了(。)利用了一些性质,比如说x被y吃意思就是x到y的距离是1,y被x吃表现为y到x的距离为2(可以通过食物链循环的方式理解,相当于y吃z,z吃x,x就吃y),xy同类就理解为没有距离。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值