题意:传送门
题解:这个题是对一段区间进行开平方运算,可以想到的是无论是那些线段树的常规操作,还是珂朵莉树都不行,如果线段树进行单点更新,那么最多只能得到50分,想想很苦恼,该如何进行操作呢?最后ysl想出因为开方最多也就是几次,然后看了一眼数据范围
如果每个数是10^12,那么最多也就是单点更新几次之后,它就总是维持1不变了,因为2的幂次非常大了,最后可以搞出如果一段区间和等于了这个区间的长度,那么直接就不用更新了,最后注意把long long开好,就nice了。
注意就是题中要注意的,注意区间两边的大小是否需要交换。
附上代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+5;
int n,m;
struct data{int l,r,sum;}tr[maxn<<2];
void build(int k,int l,int r)
{
tr[k].l=l;tr[k].r=r;
if(l==r){
int x;scanf("%lld",&x);
tr[k].sum=x;return ;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
void change(int k,int x,int y)
{
int l=tr[k].l,r=tr[k].r;
if(tr[k].sum==r-l+1){
return ;
}
if(l==r){
tr[k].sum=(int)sqrt(tr[k].sum);
return ;
}
int mid=(l+r)>>1;
if(mid>=y)change(k<<1,x,y);
else if(mid<x)change(k<<1|1,x,y);
else{
change(k<<1,x,mid);
change(k<<1|1,mid+1,y);
}
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
int ask(int k,int x,int y)
{
int l=tr[k].l,r=tr[k].r;
if(l==x&&r==y)return tr[k].sum;
int mid=(l+r)>>1;
if(mid>=y)return ask(k<<1,x,y);
else if(mid<x)return ask(k<<1|1,x,y);
else return ask(k<<1,x,mid)+ask(k<<1|1,mid+1,y);
}
int32_t main()
{
scanf("%lld",&n);
build(1,1,n);
scanf("%lld",&m);
for(int i=1;i<=m;i++){
int k,x,y;
scanf("%lld%lld%lld",&k,&x,&y);
if(x>y)swap(x,y);
if(k==0)change(1,x,y);
else printf("%lld\n",ask(1,x,y));
}
return 0;
}
然后又想了一种,就是更新时如果已经不能更新的话,那么标记下,然后如果某个节点的左右节点均已被标记,那么直接将这个节点也进行标记。
附上代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+5;
int n,a[maxn],m;
struct data{int l,r,sum;bool geng;}tr[maxn<<2];
void build(int k,int l,int r)
{
tr[k].l=l;tr[k].r=r;tr[k].geng=false;
if(l==r){
int x;
scanf("%lld",&x);
tr[k].sum=x;
return ;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
}
void change(int k,int x,int y)
{
int l=tr[k].l,r=tr[k].r;
// if(tr[k].sum==y-x+1){
// return ;
// }
if(tr[k].geng)return ;
if(l==r){
tr[k].sum=(int)sqrt(tr[k].sum);
if(tr[k].sum<=1)tr[k].geng=true;
return ;
}
int mid=(l+r)>>1;
if(mid>=y)change(k<<1,x,y);
else if(mid<x)change(k<<1|1,x,y);
else{
change(k<<1,x,mid);
change(k<<1|1,mid+1,y);
}
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
if(tr[k<<1].geng&&tr[k<<1|1].geng)tr[k].geng=true;
}
int ask(int k,int x,int y)
{
int l=tr[k].l,r=tr[k].r;
if(l==x&&r==y)return tr[k].sum;
int mid=(l+r)>>1;
if(mid>=y)return ask(k<<1,x,y);
else if(mid<x)return ask(k<<1|1,x,y);
else{
return ask(k<<1,x,mid)+ask(k<<1|1,mid+1,y);
}
}
int32_t main()
{
scanf("%lld",&n);
build(1,1,n);
scanf("%lld",&m);
for(int i=1;i<=m;i++){
int k,x,y;
scanf("%lld%lld%lld",&k,&x,&y);
if(x>y)swap(x,y);
if(k==0)change(1,x,y);
else printf("%lld\n",ask(1,x,y));
}
return 0;
}
最后就是一种特别好的思想,使用树状数组加并查集进行维护,这个题最主要在于如何维护这个区间的开平方操作最有效,就是这样如果这个点已经不能被更新,那么将他的父节点指向下一位,然后再通过路径压缩,使得速度大大加快。树状数组两行,并查集一行搞定。
附上代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int n,m,k,l,r,f[maxn],t;
ll tr[maxn],a[maxn];
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
void add(int x,ll y){while(x<=n)tr[x]+=y,x+=(x&-x);}
ll qry(int x){ll r=0;while(x)r+=tr[x],x-=(x&-x);return r;}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]),add(i,a[i]),f[i]=i;
f[n+1]=n+1;
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&k,&l,&r);
if(l>r)swap(l,r);
if(k==1)printf("%lld\n",qry(r)-qry(l-1));
else for(int i=l;i<=r;add(i,(t=(int)sqrt(a[i]))-a[i]),a[i]=t,f[i]=(a[i]==1)?i+1:i,i=(find(i)==i)?i+1:f[i]);
}
return 0;
}