线段树+优化(hdu 5828 hdu 4027)

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5828

题目意思:对于一组数据,只能进行三种操作:1 对区间[l,r]中的每个数据进行加value操作,2 对区间[l,r]中的每个数据进行开方向下取整操作 3 对于区间[l,r]中的数据进行求和操作

题目思路:开根号操作很快,对于2^64 最多8次会变成1,所以会存在很大一些数据相等的情况,对于加法操作,只需要多用一个lazy进行维护,对于没有用到的底层,只要把加法保留到上一层即可,等到用到了在进行向下更新,没有用到一直累加在上一层,但注意每一次进行add插入之时要进行更新,免得出矛盾,对于sqrt见下注释

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#define N 100050

using namespace std;
long long  sum[N*4],minn[N*4],maxx[N*4],lazy[N*4];//lazy用于记录没有下放的add数据总和,只有需要使用时才下放
void push_up(int root)//每次下层数据更新后,更新当前层数据
{
    sum[root]=sum[root<<1]+sum[root<<1|1];
    minn[root]=min(minn[root<<1],minn[root<<1|1]);
    maxx[root]=max(maxx[root<<1],maxx[root<<1|1]);
}
void push_down(int root,int length)  //每次对之前没有继续下放的数据进行更新
{
    if(lazy[root])//用于更新add没有下放的数据
    {
        lazy[root<<1]+=lazy[root];
        lazy[root<<1|1]+=lazy[root];
        sum[root<<1]+=lazy[root]*(length-length/2);
        sum[root<<1|1]+=lazy[root]*(length/2);
        maxx[root<<1]+=lazy[root];
        maxx[root<<1|1]+=lazy[root];
        minn[root<<1]+=lazy[root];
        minn[root<<1|1]+=lazy[root];
        lazy[root]=0;
    }
    if(minn[root]==maxx[root])//用于更新sqrt没有下放的数据
    {
        minn[root<<1]=minn[root<<1|1]=maxx[root<<1]=maxx[root<<1|1]=minn[root];
        sum[root<<1]=minn[root]*(length-length/2);sum[root<<1|1]=minn[root]*(length/2);
    }
}
void build(int root,int l,int r) //建树
{
    lazy[root]=0;
    if(l==r)
    {
        scanf("%lld",&sum[root]);
        minn[root]=sum[root];
        maxx[root]=sum[root];
        return;
    }
    int mid=(l+r)>>1;
    build(root<<1,l,mid);
    build(root<<1|1,mid+1,r);
    push_up(root);
}
void updateadd(int vl,int vr,int v,int root,int l,int r)//add操作
{
    if(vl<=l && r<=vr)
    {
        sum[root]+=(long long)v*(r-l+1);
        maxx[root]+=v;
        minn[root]+=v;
        lazy[root]+=v;
        return;
    }
    push_down(root,r-l+1);
    int mid=(l+r)>>1;
    if(vr>mid)
    {
        updateadd(vl,vr,v,root<<1|1,mid+1,r);
    }
    if(vl<=mid)
    {
        updateadd(vl,vr,v,root<<1,l,mid);
    }
    push_up(root);
}
void updatesqrt(int vl,int vr,int root,int l,int r)//sqrt操作
{
    if(vl<=l && r<=vr)
    {
        if((int)(sqrt(minn[root]))==(int)(sqrt(maxx[root])))
        {
            minn[root]=maxx[root]=(int)sqrt(minn[root]);
            sum[root]=minn[root]*(r-l+1);
        }
        //只有极差为1的时候才会出现加一个值再开方会出现原序列
             /*
             比如2 3 2 3 2 3然后整体加6然后开根号,又会变回2 3 2 3 2 3相当于不会出现区间数字一样的情况了,
             这样就相当于没剪枝,变成了n^2...后来发现只有极差为1的时候才会出现这种情况,极差>1的话是不可
             能维持原来的序列的,那么就可以维护一个最大值和最小值,如果极差为1的话那么区间开根号其实相当
             于区间减去了x-sqrt(x)
             */
        else if(minn[root]+1==maxx[root])
        {
            int k = maxx[root]-(int)sqrt(maxx[root]);
            updateadd(vl,vr,-k,root,l,r);
        }
        else
        {
            push_down(root,r-l+1);
            int mid = (l+r)>>1;
            if(vl<=mid)updatesqrt(vl,vr,root<<1,l,mid);
            if(vr>mid)updatesqrt(vl,vr,root<<1|1,mid+1,r);
            push_up(root);
        }
        return;
    }
    push_down(root,r-l+1);
    int mid=(l+r)>>1;
    if(vr>mid)
    {
        updatesqrt(vl,vr,root<<1|1,mid+1,r);
    }
    if(vl<=mid)
    {
        updatesqrt(vl,vr,root<<1,l,mid);
    }
    push_up(root);
}
long long query(int vl,int vr,int root,int l,int r)
{
    if(vl<=l && r<=vr)
    {
        return sum[root];
    }
    push_down(root,r-l+1);
    int mid=(l+r)>>1;
    long long ans=0;
    if(vl<=mid)
    {
        ans+=query(vl,vr,root<<1,l,mid);
    }
    if(vr>mid)
    {
        ans+=query(vl,vr,root<<1|1,mid+1,r);
    }
    return ans;
}
int main()
{
    int t;
    int n,m;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d",&n,&m);
        build(1,1,n);
        for(int i=1;i<=m;i++)
        {
            int l,r,v,op;
            scanf("%d",&op);
            if(op==1)
            {
                scanf("%d %d %d",&l,&r,&v);
                updateadd(l,r,v,1,1,n);
            }
            if(op==2)
            {
                scanf("%d %d",&l,&r);
                updatesqrt(l,r,1,1,n);
            }
            if(op==3)
            {
                scanf("%d %d",&l,&r);
                printf("%lld\n",query(l,r,1,1,n));
            }
        }
    }
    return 0;
}

类似题目: http://acm.hdu.edu.cn/showproblem.php?pid=4027

题目意思:就是上面的1,2操作,去掉了加法

思路:就是上面的思路,代码也是直接上面改的,但要主要每次case结束要空一行,并且l r 不一定满足l<r

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#define N 100050

using namespace std;
long long  sum[N*4],minn[N*4],maxx[N*4],lazy[N*4];
void push_up(int root)
{
    sum[root]=sum[root<<1]+sum[root<<1|1];
    minn[root]=min(minn[root<<1],minn[root<<1|1]);
    maxx[root]=max(maxx[root<<1],maxx[root<<1|1]);
}
void push_down(int root,int length)
{
    if(lazy[root])
    {
        lazy[root<<1]+=lazy[root];
        lazy[root<<1|1]+=lazy[root];
        sum[root<<1]+=lazy[root]*(length-length/2);
        sum[root<<1|1]+=lazy[root]*(length/2);
        maxx[root<<1]+=lazy[root];
        maxx[root<<1|1]+=lazy[root];
        minn[root<<1]+=lazy[root];
        minn[root<<1|1]+=lazy[root];
        lazy[root]=0;
    }
    if(minn[root]==maxx[root])
    {
        minn[root<<1]=minn[root<<1|1]=maxx[root<<1]=maxx[root<<1|1]=minn[root];
        sum[root<<1]=minn[root]*(length-length/2);sum[root<<1|1]=minn[root]*(length/2);
    }
}
void build(int root,int l,int r)
{
    lazy[root]=0;
    if(l==r)
    {
        scanf("%lld",&sum[root]);
        minn[root]=sum[root];
        maxx[root]=sum[root];
        return;
    }
    int mid=(l+r)>>1;
    build(root<<1,l,mid);
    build(root<<1|1,mid+1,r);
    push_up(root);
}
void updateadd(int vl,int vr,int v,int root,int l,int r)
{
    if(vl<=l && r<=vr)
    {
        sum[root]+=(long long)v*(r-l+1);
        maxx[root]+=v;
        minn[root]+=v;
        lazy[root]+=v;
        return;
    }
    push_down(root,r-l+1);
    int mid=(l+r)>>1;
    if(vr>mid)
    {
        updateadd(vl,vr,v,root<<1|1,mid+1,r);
    }
    if(vl<=mid)
    {
        updateadd(vl,vr,v,root<<1,l,mid);
    }
    push_up(root);
}
void updatesqrt(int vl,int vr,int root,int l,int r)
{
    if(vl<=l && r<=vr)
    {
        if((int)(sqrt(minn[root]))==(int)(sqrt(maxx[root])))
        {
            minn[root]=maxx[root]=(int)sqrt(minn[root]);
            sum[root]=minn[root]*(r-l+1);
        }
        //只有极差为1的时候才会出现加一个值再开方会出现原序列
             /*
             比如2 3 2 3 2 3然后整体加6然后开根号,又会变回2 3 2 3 2 3相当于不会出现区间数字一样的情况了,
             这样就相当于没剪枝,变成了n^2...后来发现只有极差为1的时候才会出现这种情况,极差>1的话是不可
             能维持原来的序列的,那么就可以维护一个最大值和最小值,如果极差为1的话那么区间开根号其实相当
             于区间减去了x-sqrt(x)
             */
        else if(minn[root]+1==maxx[root])
        {
            int k = maxx[root]-(int)sqrt(maxx[root]);
            updateadd(vl,vr,-k,root,l,r);
        }
        else
        {
            push_down(root,r-l+1);
            int mid = (l+r)>>1;
            if(vl<=mid)updatesqrt(vl,vr,root<<1,l,mid);
            if(vr>mid)updatesqrt(vl,vr,root<<1|1,mid+1,r);
            push_up(root);
        }
        return;
    }
    push_down(root,r-l+1);
    int mid=(l+r)>>1;
    if(vr>mid)
    {
        updatesqrt(vl,vr,root<<1|1,mid+1,r);
    }
    if(vl<=mid)
    {
        updatesqrt(vl,vr,root<<1,l,mid);
    }
    push_up(root);
}
long long query(int vl,int vr,int root,int l,int r)
{
    if(vl<=l && r<=vr)
    {
        return sum[root];
    }
    push_down(root,r-l+1);
    int mid=(l+r)>>1;
    long long ans=0;
    if(vl<=mid)
    {
        ans+=query(vl,vr,root<<1,l,mid);
    }
    if(vr>mid)
    {
        ans+=query(vl,vr,root<<1|1,mid+1,r);
    }
    return ans;
}
int main()
{
    int t=1;
    int n,m;
    while(scanf("%d",&n)!=EOF)
    {
        printf("Case #%d:\n",t++);
        build(1,1,n);
        scanf("%d",&m);
        for(int i=1;i<=m;i++)
        {
            int l,r,v,op;
            scanf("%d",&op);
            /*if(op==3)
            {
                scanf("%d %d %d",&l,&r,&v);
                updateadd(l,r,v,1,1,n);
            }*/
            scanf("%d %d",&l,&r);
            if(l>r){swap(l,r);}
            if(op==0)
            {
                updatesqrt(l,r,1,1,n);
            }
            if(op==1)
            {
                printf("%lld\n",query(l,r,1,1,n));
            }
        }
        printf("\n");
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值