算法:并查集+树状数组/分块/线段树
难度:(NOIP+)
简述题目:区间开方,区间求和
区间求和很容易想到树状数组/线段树,可是区间开方怎么搞呢?暴力O(n*n),TLE到飞
我们可以发现,一个10^12的数,最多开6次方(向下取整)可以变成1,变成1之后,无论开多少次方都是1,所以就可以跳过这个数,这个可以用并查集搞,开始时父亲都指向自己,如果变成1,就把父亲指向下一个位置即可。修改的时候相当于跳着修改!
代码如下:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <queue>
#include <cmath>
#define ll long long
#define N 200015
using namespace std;
ll a[N],c[N];
int fa[N];
int findf(int x)
{
if(x==fa[x]) return x;
return fa[x]=findf(fa[x]);
}
int lowbit(int x)
{
return x & (-x);
}
int l,r,k,n;
void add(int x,ll val)
{
while(x<=n)
{
c[x]+=val;
x+=lowbit(x);
}
}
ll query(int x)
{
ll ret=0;
while(x)
{
ret+=c[x];
x-=lowbit(x);
}
return ret;
}
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n+5/*防止findf(n+1) RE*/;i++)
{
fa[i]=i;
}
for(int i = 1;i <= n;i++)
{
scanf("%lld",&a[i]);
add(i,a[i]);//忘记add,我在玩什么?
if(a[i]<=1)
{
fa[i]=findf(findf(i)+1);
}
}
int m;
scanf("%d",&m);
while(m--)
{
scanf("%d%d%d",&k,&l,&r);
if(l>r) swap(l,r);
if(k==2)
{
for(int i = findf(l);i<=r;i=findf(i+1/*指向下一位*/))
{
ll tmp=sqrt(a[i]);
add(i,tmp-a[i]);//(sqrt(x)-x)+x=sqrt(x)
a[i]=tmp;
if(a[i]<=1)
{
fa[i]=findf(i+1);//压缩路径
}
}
}else
{
printf("%lld\n",query(r)-query(l-1));
}
}
return 0 ;
}