BZOJ3038上帝造题的七分钟2(线段树优化/树状数组+并查集维护)

题意:传送门

题解:这个题是对一段区间进行开平方运算,可以想到的是无论是那些线段树的常规操作,还是珂朵莉树都不行,如果线段树进行单点更新,那么最多只能得到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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值