并查集4

并查集之

找左右第一个大于(小于)x的数

模板:求左右第一个小于它的数
for(int i=1;i<=n;i++){
	a[i].x=read(),a[i].id=i;
	fa[i]=ml[i]=i;
}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++){
	int cur=a[i].id;
	vis[cur]=1;
	if(cur>1&&vis[cur-1]){
		fa[cur-1]=cur;
		ml[cur]=ml[cur-1];
	}
	if(cur<n&&vis[cur+1]){
		fa[cur]=find(cur+1);
		ml[fa[cur]]=ml[cur];
	}
	r[i]=find(cur),l[i]=ml[r[i]];
    }

先将原数组从大到小排序,保留下标,这样保证后插入的比先插入的小

如果新插入的左右都有集合,那就合并这两个集合,这个集合的根就是r[i],最左边就是l[i]

ml[i]为以i为根的并查集的最左边的位置 

6 2 3 1 4 7 5
第一次:7是独立集合
第二次:6是独立集合
第三次,5,把7和5合并,根是5,同时记录该集合最左值是7的位置
第四次,4,它左边没有访问不管,把右边合并一个集合,同时记录集合最左值是4的位置
第五次, 3,是独立集合
第六次2,合并,6,3为一个集合根是3,最左值是6的位置
第7次,1,左边两边都访问过,合并两边集合,根是5,最左值就是6的位置

上帝造题的7分钟2 

区间开方,区间求和

n ≤ 100000 ai ≤ 10000000

样例输入

10
1 2 3 4 5 6 7 8 9 10
5
0 1 10
1 1 10
1 1 5
0 5 8
1 4 8

样例输出

19
7
6

分析

注意到1开方还是1,10^12次方开几次也都变成1了

于是我们只修改范围内>1的数,树状数组单点修改

如何快速找到?->并查集维护一个数左边第一个大于1的数

边看代码边想

#include<bits/stdc++.h>
#define N 100005
#define LL long long
using namespace std;
LL fa[N],c[N*4],n,m,a[N];
LL read(){
	LL cnt=0;char ch=0;
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))cnt=cnt*10+(ch-'0'),ch=getchar();
	return cnt;
}
LL find(LL x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void add(LL p,LL v){for(;p<=n;p+=(p&-p)) c[p]+=v;}
LL quary(LL p){LL ans=0;for(;p;p-=(p&-p)) ans+=c[p];return ans;}
int main()
{
	n=read();
	for(LL i=1;i<=n;i++){
		a[i]=read(),add(i,a[i]),fa[i]=i;
	}fa[n+1]=n+1; 
	m=read();
	while(m--){
		LL op=read(),l=read(),r=read();
		if(l>r)swap(l,r);
		if(op==1) cout<<quary(r)-quary(l-1)<<endl;
		else{
			for(LL j=l;j<=r;j=(find(j)==j)?j+1:fa[j]){//直接跳到下一个>1的数
				LL tmp=(LL)(sqrt(a[j]));
				add(j,tmp-a[j]);
				a[j]=tmp;
				fa[j]=(a[j]<=1)?j+1:j;
                                //这个位置已经<=1,把fa连到j+1,这样find(j)时可以找到后面的1
			}
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/sslz_fsy/article/details/82110830