HDU 6562 2018CCPC吉林赛站H题(硬核线段树)

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

 

题目大意:有n个空串,两种操作,第一种操作是让l到r区间内的串开头结尾加上d,比如之前是33,d是5,那就变成5335,另一个操作是l到r区间内的所有串求和并输出

 

题目思路:讲道理,直接自闭,太难打了。。线段树需要维护五个变量,分别是表示的区间的和sum,表示的区间的数字长度len的\sum_{i=l}^{r}10^{len_{i}}len,表示区间右边应该加上的数字的延迟标记为lazr,左边应该加上的数字的延迟标记为lazl,设这些数字的长度的长度为len_{i},那么lazylen就是\sum_{i=l}^{r}10^{len_{i}}

查询是最好写的,直接按照普通的线段树加sum就完事了

更新的时候,首先更新sum,因为原先的数字需要乘上10为新添的数字留个空位,所以第一项就是sum*10,然后由于这一区间内所有数字都加上了val,也就是第二项需要是(r-l+1)*val,第三项就是在最左边加上val,由于之前维护的len是\sum_{i=l}^{r}10^{len_{i}},所以我们只需要在len的基础上*10就是各个区间内的数字要放数字的位置。比如之前是33的话,val是5,之前的len是10^2=100,乘上10就是千位的位置,正好与我们想要的位置符合。

然后更新len,因为多出来俩数字,也就是多了俩0,乘个100即可

接着更新lazr,之前的lazr*10给新人空位置,然后+val即可

lazl的话是往原来数字的左边加,由于lazlen已经记录了当前应该往哪个位加,直接lazl+val*lazlen就完事

 

lazlen是每次多一个数字,所以乘个10即可

接着讲最最最难的延迟标记下传,更新孩子的sum的时候,先让孩子的sum乘以父亲的lazlen,使得孩子的sum右边留下空间放父亲的lazr,然后加上孩子的(r-l+1)*父亲的lazr,左边加就是父亲的lazl*孩子的len再乘上父亲的lazlen。

孩子的len就是本身乘两次父亲的lazlen,因为lazlen是lazr和lazl各自的长度,所以总长是这俩共同贡献,需要乘两次

lazl就是父亲的lazl跟到孩子的lazl后面,也就是父亲的lazl*孩子的lazlen+孩子之前的lazl

lazr就是孩子后面空出父亲的lazlen的距离,给父亲的lazr留位置,也就是孩子的lazr*父亲的lazlen+孩子之前的lazr

lazlen就是孩子的lazlen乘上父亲的lazlen,因为它是单单一边的长度,所以只用一遍

 

以下是代码:

#include<iostream>


using namespace std;
#define ll long long
#define rep(i,a,b) for(ll i=a;i<=b;i++)
#define per(i,a,b) for(ll i=a;i>=b;i--)
#define ll long long
const ll MOD = 1e9+7;
const ll MAXN = 1e5+5;
char s[10];
struct node{
    ll l,r,sum,len,lazl,lazr,lazlen;
}a[MAXN<<2];
void build(ll rt,ll l,ll r){
    a[rt].l=l,a[rt].r=r;
    a[rt].sum=a[rt].lazl=a[rt].lazr=0;
    a[rt].lazlen=1;
    if(l==r){
        a[rt].len=1;
        return;
    }
    ll mid=(l+r)>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    a[rt].len=(a[rt<<1].len+a[rt<<1|1].len)%MOD;
}
void spread(ll rt){
    if(a[rt].lazlen>1){
        a[rt<<1].sum=((ll)a[rt].lazlen*a[rt<<1].sum%MOD+(ll)a[rt].lazr*(a[rt<<1].r-a[rt<<1].l+1)%MOD+(ll)a[rt].lazl*a[rt<<1].len%MOD*a[rt].lazlen%MOD)%MOD;
        a[rt<<1|1].sum=((ll)a[rt].lazlen*a[rt<<1|1].sum%MOD+(ll)a[rt].lazr*(a[rt<<1|1].r-a[rt<<1|1].l+1)%MOD+(ll)a[rt].lazl*a[rt<<1|1].len%MOD*a[rt].lazlen%MOD)%MOD;

        a[rt<<1].len=(ll)a[rt<<1].len*a[rt].lazlen%MOD*a[rt].lazlen%MOD;
        a[rt<<1|1].len=(ll)a[rt<<1|1].len*a[rt].lazlen%MOD*a[rt].lazlen%MOD;

        a[rt<<1].lazl=((ll)a[rt].lazl*a[rt<<1].lazlen%MOD+a[rt<<1].lazl)%MOD;
        a[rt<<1|1].lazl=((ll)a[rt].lazl*a[rt<<1|1].lazlen%MOD+a[rt<<1|1].lazl)%MOD;

        a[rt<<1].lazr=((ll)a[rt<<1].lazr*a[rt].lazlen%MOD+a[rt].lazr)%MOD;
        a[rt<<1|1].lazr=((ll)a[rt<<1|1].lazr*a[rt].lazlen%MOD+a[rt].lazr)%MOD;

        a[rt<<1].lazlen=((ll)a[rt<<1].lazlen*a[rt].lazlen)%MOD;
        a[rt<<1|1].lazlen=((ll)a[rt<<1|1].lazlen*a[rt].lazlen)%MOD;

        a[rt].lazl=a[rt].lazr=0;
        a[rt].lazlen=1;
    }
}
void update(ll rt,ll l,ll r,ll val){
    if(a[rt].l>=l&&a[rt].r<=r){
        a[rt].sum=((a[rt].r-a[rt].l+1)*val%MOD+a[rt].sum*10ll%MOD+a[rt].len*val*10ll%MOD)%MOD;
        a[rt].len=a[rt].len*100ll%MOD;
        a[rt].lazr=(a[rt].lazr*10ll+val)%MOD;
        a[rt].lazl=(a[rt].lazl+a[rt].lazlen*(ll)val)%MOD;
        a[rt].lazlen=a[rt].lazlen*10ll%MOD;
        return;
    }
    spread(rt);
    ll mid=(a[rt].l+a[rt].r)>>1;
    if(l<=mid)update(rt<<1,l,r,val);
    if(r>mid)update(rt<<1|1,l,r,val);
    a[rt].sum=(a[rt<<1].sum+a[rt<<1|1].sum)%MOD;
    a[rt].len=(a[rt<<1].len+a[rt<<1|1].len)%MOD;
}
ll query(ll rt,ll l,ll r){
    if(a[rt].l>=l&&a[rt].r<=r){
        return a[rt].sum;
    }
    spread(rt);
    ll mid=(a[rt].l+a[rt].r)>>1;
    ll ans=0;
    if(l<=mid)ans=(ans+query(rt<<1,l,r))%MOD;
    if(r>mid)ans=(ans+query(rt<<1|1,l,r))%MOD;
    return ans;
}
int main()
{
    ll t,n,m,l,r,d;
    scanf("%lld",&t);
    rep(_,1,t){
        printf("Case %lld:\n",_);
        scanf("%lld%lld",&n,&m);
        build(1,1,n);
        while(m--){
            scanf("%s",s);
            if(s[0]=='w'){
                scanf("%lld%lld%lld",&l,&r,&d);
                update(1,l,r,d);
            }
            else{
                scanf("%lld%lld",&l,&r);
                ll ans=query(1,l,r);
                printf("%lld\n",ans);
            }
        }
    }
    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值