线段树区间加一个等比数列

该博客介绍了如何处理一个在线段树中进行区间加法的问题,其中每次加上的数值构成一个等比数列。通过利用等比数列的求和公式,可以将懒惰标记更新为等比数列的首项,从而简化区间更新。在传递标记时,区分左右子树的不同处理方式,并强调在处理大整数乘法时需要使用快速乘防止溢出。博客提供了一种根据题目条件优化线段树算法的方法,并提到了暴力解法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大概是这样的:

  1. 给出一个长度为nnn的序列,mmm次询问,qqq为参数,有两种操作
  2. 给区间∀i∈[l,r]{\forall}i\in[l,r]i[l,r]的数字加上qi−lq^{i-l}qil
  3. 询问区间[l,r][l,r][l,r]的数字和
  4. n,m&lt;=105,1&lt;q&lt;=109n,m&lt;=10^5,1&lt;q&lt;=10^9n,m<=1051<q<=109
    分析:
    既然题目说q&gt;1q&gt;1q>1,那么就可以用等比数列求和公式来计算加了多少了,而且对于所有的操作,q都是一致的,那么懒标记可以记录这段区间的要加的等比数列的首项就可以!
    下传标记的时候注意左子树可以直接加过来,因为他们的首项相同,右子树就需要补上qmid+1−lq^{mid+1-l}qmid+1l了。

听老师YY的一道题,也没有找到交的地方,自己写了写暴力对拍了好多数据都过了,需要注意乘法的时候需要用快速乘,因为逆元还有标记还有次幂数组乘起来会炸long long。

输入格式就是:n,m,qn,m,qn,m,q
序列元素
mmm次操作,id,l,rid,l,rid,l,r,id为1是修改,2是查询。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=1e5+7;

const int mod=1e9+7;

ll ci[maxn];

ll sum[maxn<<2|1];
ll lazy[maxn<<2|1];
ll q;
ll ni;

ll quick(ll a,ll b){
    ll res=1;
    while(b){
        if(b&1) res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}

void pushup(int k){
    sum[k]=sum[k<<1]+sum[k<<1|1];
    sum[k]%=mod;
}

ll ksc(ll x,ll y,ll mod) {
    x%=mod;
    y%=mod;
    return (x*y-(ll)((long double)x/mod*y)*mod+mod)%mod;
}

void build(int l,int r,int k){
    lazy[k]=0;
    if(l==r){
        scanf("%lld",&sum[k]);
        return ;
    }
    int mid=(l+r)>>1;
    build(l,mid,k<<1);
    build(mid+1,r,k<<1|1);
    pushup(k);
}

void pushdown(int l,int r,int k){
    if(lazy[k]){
        int mid=(l+r)>>1;
        lazy[k<<1]+=lazy[k];
        lazy[k<<1]%=mod;
        lazy[k<<1|1]+=lazy[k]*ci[mid-l+1]%mod;
        lazy[k<<1|1]%=mod;
        sum[k<<1]+=ksc(lazy[k]*ci[mid-l+1]-lazy[k]+mod,ni,mod);
        sum[k<<1|1]+=ksc(lazy[k]*ci[r-l+1]-lazy[k]*ci[mid-l+1]+mod,ni,mod);
        sum[k<<1]%=mod;
        sum[k<<1|1]%=mod;
        lazy[k]=0;
    }
}

void updata(int l,int r,int k,int L,int R){
    if(l>=L&&r<=R){
        sum[k]+=ksc(ci[r-L+1]-ci[l-L]+mod,ni,mod);
        sum[k]%=mod;
        lazy[k]+=ci[l-L];
        lazy[k]%=mod;
        return ;
    }
    pushdown(l,r,k);
    int mid=(l+r)>>1;
    if(L<=mid) updata(l,mid,k<<1,L,R);
    if(R>mid) updata(mid+1,r,k<<1|1,L,R);
    pushup(k);
}

ll myfind(int l,int r,int k,int L,int R){
    if(l>=L&&r<=R) return sum[k];
    pushdown(l,r,k);
    int mid=(l+r)>>1;
    ll res=0;
    if(L<=mid) res+=myfind(l,mid,k<<1,L,R);
    if(R>mid) res=(res+myfind(mid+1,r,k<<1|1,L,R))%mod;
    pushup(k);
    return res%mod;
}

int main(){
    freopen("in.txt","r",stdin);
    freopen("out1.txt","w",stdout);
    int n,m,l,r,id;
    scanf("%d%d%lld",&n,&m,&q);
    ni=quick(q-1,mod-2);
    ci[0]=1;
    for(int i=1;i<maxn;++i)
        ci[i]=ci[i-1]*q%mod;
    build(1,n,1);
    while(m--){
        scanf("%d%d%d",&id,&l,&r);
        if(id==1) updata(1,n,1,l,r);
        else printf("%lld\n",myfind(1,n,1,l,r));
    }
    return 0;
}

暴力:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=1e5+7;

ll a[maxn];

const int mod=1e9+7;

ll ci[maxn];

int main(){
    freopen("in.txt","r",stdin);
    freopen("out2.txt","w",stdout);
	int n,m,id,l,r;
	ll q;
	scanf("%d%d%lld",&n,&m,&q);
	ci[0]=1;
	for(int i=1;i<maxn;++i) ci[i]=ci[i-1]*q%mod;
	for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
	while(m--){
		scanf("%d%d%d",&id,&l,&r);
		if(id==1) for(int i=l;i<=r;++i) a[i]=(a[i]+ci[i-l])%mod;
		else{
			ll res=0;
			for(int i=l;i<=r;++i) res=(res+a[i])%mod;
			printf("%lld\n",res);
		}
	}



	return 0;
}
造数据
/*
#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5;
const int qq=1e9;
const int mod=1e9+7;

int main(){
    int n,m,q,id,l,r;
    freopen("in.txt","w",stdout);
    srand(unsigned(time(0)));
    n=rand()%maxn+1;
    m=rand()%maxn+1;
    q=rand()%qq;
    if(q<=1) q=2;
    printf("%d %d %d\n",n,m,q);
    for(int i=1;i<=n;++i)
        printf("%d ",rand()%qq);
    printf("\n");
    while(m--){
        id=rand()%2+1;
        l=rand()%n+1;
        r=rand()%n+1;
        if(l>r) swap(l,r);
        printf("%d %d %d\n",id,l,r);
    }

    return 0;
}
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值