题目链接http://www.lydsy.com/JudgeOnline/problem.php?id=3211
Description
Input
Output
每次x=1时,每行一个整数,表示这次旅行的开心度
Sample Input
4
1 100 5 5
5
1 1 2
2 1 2
1 1 2
2 2 3
1 1 4
Sample Output
101
11
11
HINT
对于100%的数据, n ≤ 100000,m≤200000 ,data[i]非负且小于10^9
题目大意:给定一个长度为n的序列,支持区间开平方(向下取整)和区间求和
根号是不支持区间修改的,我们可以选择单点修改区间查询的树状数组,但是这样是O(n^2)的,时间上接受不了
发现一个数x最多开loglogx次根号就会变为1,也就是一个int范围内的数最多开6次根号就会变为1 ,如果某个数变为1,以后可以不对这个数进行操作,修改的总时间复杂度为O(nloglog1e9)
对于每一个已经为0或1的点,以后的每次操作都可以直接跳过这个点,这种操作可以用并查集来实现(同BZOJ2054,疯狂的馒头)对于每一个非0非1的节点i,使f[i]=i,如果某次操作将编号为i的点变为1,则更改f[i]为i+1,在区间修改时,每次从节点i跳到f[i+1]即可
细节1:f[n+1]=n+1
细节2:答案会爆int
某sb这题因为没处理好细节T了15次,WA了3次,CE了1次…..
当然这题也可以用线段树打标记来实现。。但因为并查集好写,所以就不写线段树的做法了23333
代码如下:
#include<ctype.h>
#include<cstdio>
#include<cmath>
#define N 100500
#define int long long
using namespace std;
inline int read(){
int f=1,x=0;
char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return x*f;
}
int n,m,k,l,r,t,q[N],a[N],f[N];
int lowbit(int x){return x&-x;}
void add(int x,int v){
while(x<=n){
q[x]=q[x]+v;
x=x+lowbit(x);
}
}
long long sum(int x){
long long tmp=0;
while(x){
tmp=tmp+q[x];
x=x-lowbit(x);
}
return tmp;
}
int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
main(){
n=read();
for(int i=1;i<=n;i++) a[i]=read(),add(i,a[i]),f[i]=a[i]<=1?i+1:i;
f[n+1]=n+1;
m=read();
for(int i=1;i<=m;i++){
k=read();l=read();r=read();
if(k==1) printf("%lld\n",sum(r)-sum(l-1));
else{
for(int j=l;j<=r;j=find(j+1)){
int tmp=(int)sqrt(a[j]);
add(j,tmp-a[j]);
a[j]=tmp;
if(a[j]<=1) f[j]=find(j+1);
}
}
}
return 0;
}