并查集之
找左右第一个大于(小于)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;
}