LOJ #6283. 数列分块入门 7

9 篇文章 1 订阅

这个题目有两种方法,一种是利用线段树的思想进行标记下传操作,另一种则是经过数学推导使得对块标记不做改变

 

记 add[] 为对整个块的每一个数都加 add,mul[] 为对整个块内的每一个数都 乘以 mul  

标记下传代码:  

  • 在对整个区间进行操作 x 时,区间加法只需操作 add 数组,区间乘法修改 add=add*x mul=mul*x 这两个数组
  • 当对部分区间进行 +x 时,任意一个数 a[i]=a[i]*mul+add ,所以首先进行下放操作,然后修改  a[i] 的值
  • 当对部分区间进行 *x 时,任意一个数 a[i]=a[i]*mul+add ,  所以还是要对区间进行下放操作,然后修改 a[i] 的值
const ll mod=10007;
const int N=1e5+5;

    int i,j,k;
    int n,m,t;
    int a[N];
    int bel[N],block,num,L[N],R[N];
    int sum[N],tag[N]; //sum 表示块+,tag 表示块*

void build()
{
    block=sqrt(n);
    num=n/block; if(n%block) num++;
    for(int i=1;i<=num;i++){
        int l=(i-1)*block+1;
        int r=min(i*block,n);
        L[i]=l,R[i]=r;
        sum[i]=0,tag[i]=1;
        for(int j=l;j<=r;j++) bel[j]=i;
    }
}

void rep(int &a)
{
    a%=mod;
    a+=mod;
    a%=mod;
}

void push_down(int x)
{
    if(sum[x]==0 && tag[x]==1) return ;
    int l=L[x],r=R[x];
    for(int i=l;i<=r;i++){
        a[i]*=tag[x]; rep(a[i]);
        a[i]+=sum[x]; rep(a[i]);
    }
    tag[x]=1,sum[x]=0;
}

void add(int l,int r,int w)
{
    int st=bel[l],en=bel[r];
    if(st==en){
        push_down(st);
        for(int i=l;i<=r;i++) a[i]+=w,rep(a[i]);
    } else{
        push_down(st);
        for(int i=l;i<=R[st];i++) a[i]+=w,rep(a[i]);
        push_down(en);
        for(int i=L[en];i<=r;i++) a[i]+=w,rep(a[i]);
        for(int i=st+1;i<=en-1;i++) sum[i]+=w,rep(sum[i]);
    }
}


void mul(int l,int r,int w)
{
    int st=bel[l],en=bel[r];
    if(st==en){
        push_down(st);
        for(int i=l;i<=r;i++) a[i]*=w,rep(a[i]);
    } else{
        push_down(st);
        for(int i=l;i<=R[st];i++) a[i]*=w,rep(a[i]);
        push_down(en);
        for(int i=L[en];i<=r;i++) a[i]*=w,rep(a[i]);
        for(int i=st+1;i<=en-1;i++){
            tag[i]*=w;
            sum[i]*=w;
            if(tag[i]%mod) tag[i]%=mod;
            sum[i]%=mod;
        }
    }
}

int query(int pos)
{
    int x=bel[pos];
    int ans=a[pos]*tag[x]%mod+sum[x];
    return ans%mod;
}

int main()
{
    //IOS;
    while(~sd(n)){
        for(int i=1;i<=n;i++) sd(a[i]),a[i]%=mod;
        build();
        while(n--){
            int opt=read(),l=read(),r=read(),w=read();
            if(opt==1){ mul(l,r,w); }
            else if(opt==2){ pd(query(r)); }
            else add(l,r,w);
        }
    }
    //PAUSE;
    return 0;
}

 

 数学求解技巧:

在对部分区间进行更新时,每次都要进行下传操作,复杂度过高,能不能不进行操作,而且在修改任意值的时候,同时区间的性质不发生改变呢

  • 当对部分区间进行加法操作时,a[i] 实际上为 a[i]*mul+add ,如果在 a[i] 上单独加 x,是无法直接加的,但是将 x 变为 \frac{x}{mul} 这样就可以直接加入其中了
  • --------结束
  • 当对部分区间进行乘法操作时,a[i] 实际上为 a[i]*mul+add , 如果在 a[i] 上单独乘以 x,则有 (a[i]*mul+add)*x=>a[i]*mul*x+add*x
  • 但是我们不想让标记下传,也不想要修改区间的性质,所以有  a[i]*mul*x+add*x= mul*y+add
  • 求解 y,解释我们想要得到的,y=a*x+\frac{add*x-add}{mul},将 y 把  a[i] 替换即可
  • --------结束

最后需要注意的是,由于本题涉及求余,所以式子中的所有除法运算都要转换成逆元,由于 mod=10007 ,所以利用费马小定理预处理即可

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值