洛谷P2023 [AHOI2009]维护序列——线段树,数学知识

题目:https://www.luogu.org/problemnew/show/P2023

分析:

大方向是线段数,难点在于如何处理懒惰标记下传。

思路一:

结点k存有mul[k],add[k],sum[k],即乘的系数、加的系数、和的值。

现在对于这个结点对应区间所有元素需要加上b2,(相当于先乘1,再加b2),或者乘上a2(相当于先乘a2,再加0),

根据数学知识,由a2(a1x+b1)+b2=a1a2x+a2b1+b2得到

sum[k]=a2*sum[k]+b2*(r-l+1),mul[k]=a2*mul[k],add[k]=a2*add[k]+b2,

接着令这个结点的mul[k]=1,add[k]=0,即lazy_tag清零。

关键代码如下:

void Add_Mul(int k,int l,int r,long long a,long long b){
	mul[k]=mul[k]*a%p;
	add[k]=(add[k]*a%p+b%p)%p;
	sum[k]=(a*sum[k]%p+b*(r-l+1)%p)%p;
}
void pushdown(int k,int l,int r){
    if(add[k]==0 && mul[k]==1 || (l==r) ) return;
    	//如果没有标记,或是叶子,则直接返回 
    int mid=(l+r)/2;
    Add_Mul(2*k,l,mid,mul[k],add[k]);
    Add_Mul(2*k+1,mid+1,r,mul[k],add[k]);
    mul[k]=1;//标记清零 
    add[k]=0;
}

思路二:

先处理乘的系数, 清零乘的标记,再处理加的系数,清零加的标记。

关键代码如下:

void Add(int k,int l,int r,int v){
    add[k]=(add[k]+v)%p;
    sum[k]=(sum[k]+(long long)v*(r-l+1))%p;
}
void Mul(int k,int l,int r,int v){
    add[k]=(add[k]*v)%p;
    mul[k]=mul[k]*v%p;
    sum[k]=sum[k]*v%p;
}
void pushdown(int k,int l,int r,int mid){
	if(add[k]==0 && mul[k]==1) return ;
    Mul(k*2,l,mid,add2[k]);
    Mul(k*2+1,mid+1,r,add2[k]);
    mul[k]=1;//清零乘的标记
    Add(k*2,l,mid,add1[k]);
    Add(k*2+1,mid+1,r,add1[k]);
    add[k]=0;//清零和的标记
}

TLE的思路:

如果当前结点有标记,否则传给儿子,

如果儿子还有标记,则再往下传,一直下传到结点没有标记为止,然后返回时清零。

会T掉5个或6个点。

注意事项:要开long long,否则会RE。

AC代码(思路一)如下:

#include<cstdio>
#include<iostream>
using namespace std;
const int Maxn=1e5+5;
int n,p,m,a[Maxn];
long long add[4*Maxn],mul[4*Maxn],sum[4*Maxn];
void build_tree(int k,int l,int r){
    if(l==r){
        mul[k]=1;
    	add[k]=0;
        sum[k]=a[l]%p;
        return;
    }
    int mid=(l+r)/2;
    build_tree(2*k,l,mid);
    build_tree(2*k+1,mid+1,r);
    mul[k]=1;add[k]=0;
    sum[k]=(sum[2*k]+sum[2*k+1])%p;
}
void Add_Mul(int k,int l,int r,long long a,long long b){
	mul[k]=mul[k]*a%p;
	add[k]=(add[k]*a%p+b%p)%p;
	sum[k]=(a*sum[k]%p+b*(r-l+1)%p)%p;
}
void pushdown(int k,int l,int r){
    if(add[k]==0 && mul[k]==1 || (l==r) ) return;
    	//如果没有标记,或是叶子,则直接返回 
    int mid=(l+r)/2;
    Add_Mul(2*k,l,mid,mul[k],add[k]);
    Add_Mul(2*k+1,mid+1,r,mul[k],add[k]);
    mul[k]=1;add[k]=0;//标记清零   
}
void modify(int k,int l,int r,int x,int y,long long a,long long b){
    if(l>y || r<x) return;
    pushdown(k,l,r);
    if(l>=x && r<=y) {
        Add_Mul(k,l,r,a,b);
        return;//切勿漏写!! 
    }
    int mid=(l+r)/2;
    modify(2*k,l,mid,x,y,a,b);
    modify(2*k+1,mid+1,r,x,y,a,b);
    sum[k]=(sum[2*k]+sum[2*k+1])%p;
}
long long query(int k,int l,int r,int x,int y){
    if(l>y || r<x) return 0;
    if(l>=x && r<=y) return sum[k];
    pushdown(k,l,r);
    int mid=(l+r)/2;
    return ( query(2*k,l,mid,x,y)+query(2*k+1,mid+1,r,x,y) )%p;
}
int main(){
    cin>>n>>p;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    build_tree(1,1,n);
    print();
    cin>>m;
    int id,x,y;
	long long c;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&id,&x,&y);
        if(id==1){
            scanf("%lld",&c);
            modify(1,1,n,x,y,c,0);
        }
        else if(id==2){
            scanf("%lld",&c);
            modify(1,1,n,x,y,1,c);
        }
        else printf("%lld\n",query(1,1,n,x,y));

    }	
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值