ZOJ 3998(线段树)

本文介绍了一种使用线段树解决特定区间更新及查询问题的方法,包括两种类型的区间更新操作和一种区间乘积查询操作,所有操作需考虑模运算。

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

传送门

题面:

Yet Another Data Structure Problem

Time Limit: 5 Seconds       Memory Limit: 65536 KB

Given  integers . There are 3 types of operations:

  • 1 l r v: Multiply  by ;
  • 2 l r k: Change each element in  to ;
  • 3 l r: Query the multiplication of  modulo 1,000,000,007.

For each query, please output the answer.

Input

There are multiple test cases. The first line of the input is an integer  (), indicating the number of test cases. For each test case:

The first line contains two integers  () and  (), indicating the number of the given integers and the number of operations.

The second line contains  integers  (), indicating the given integers.

The first integer on each of the following  lines will be  (), indicating the type of operation.

  • If  equals 1, then three integers  () follow, indicating the first type of operation.
  • If  equals 2, then three integers  () follow, indicating the second type of operation.
  • If  equals 3, then two integers  () follow, indicating an query.

It's guaranteed that neither the sum of  nor the sum of  over all test cases will exceed .

Output

For each query, output one line containing one integer, indicating the answer.

Sample Input
1
5 5
1 2 1 2 1
3 2 4
1 1 5 2
3 2 4
2 1 1 4
3 1 1
Sample Output
4
32
16

题目描述:

    给你一个大小为n的数组,让你进行三种操作。1:将区间l到r的数全都乘上v;2:将区间l到r的数全都变成ai^k;3:求出区间l到r的数的乘积。最后的结果模上1e9+7。

题目分析:

    因为涉及区间更新以及区间求和,因此我们不难想到用线段树去维护区间乘积。

    我们可以发现,第一种操作中,如果更新整个区间[l,r],则等价于在sum[l,r]的基础上再乘上v^(r-l+1);而在第二种操作中,如果更新整个区间[l,r],则等价于对于在区间[l,r]中的每一个a[i],都要把它变成原来的a[i]^k。我们可以发现,两种操作中,第一种操作时只跟底数v有关的,第二种操作是只与质数k有关的,因此我们可以想到将底数和指数分别作为两个lazy标记分别去维护两种更新操作。

    假设a[i]是底数的lazy标记,x[i]是指数的lazy标记。那么倘若a[i]需要进行push_down操作,我们只需要将a[i]的值像类似区间和更新的操作一样push_down即可,同时再将该区间的区间积乘上pow(a[i],size)即可。

    而倘若要对x[i]进行push_down操作,首先我们要知道,因为最后的结果要取模,而根据费马小定理,  ,我们知道每次对指数x[i]%(mod-1)更新即可。而对于a[i]以及结点的值变为对应的x[i]次即可。

    ps:这个题时间卡的很紧,必须得用读入优化。

#include <bits/stdc++.h>
#define maxn 2000005
using namespace std;
typedef long long ll;
const int mod=1e9+7;
ll sum[maxn];
ll a[maxn];
ll x[maxn];
ll read()
{
	ll ret=0;
	char ch=getchar();
	while(ch>'9'||ch<'0')ch=getchar();
	while(ch>='0'&&ch<='9')
	{
		ret=ret*10+ch-'0';
		ch=getchar();
	}
	return ret;
}
ll powmod(ll a,ll b){
    int res=1;
    while(b){
        if(b&1) res=1ll*a*res%mod;
        b>>=1;
        a=1ll*a*a%mod;
    }
    return res;
}
void push_up(int rt){
    sum[rt]=1ll*sum[rt<<1]*sum[rt<<1|1]%mod;
}
void build(int l,int r,int rt){
    a[rt]=x[rt]=1;
    if(l==r){
        sum[rt]=read();
        //scanf("%lld",&sum[rt]);
        return;
    }
    int mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    push_up(rt);
}
void push_down(int rt,int ln,int rn){
    if(x[rt]>1){//若指数能进行push_down
        a[rt<<1]=powmod(a[rt<<1],x[rt]);
        a[rt<<1|1]=powmod(a[rt<<1|1],x[rt]);//更新底数的值
        
        x[rt<<1]=1ll*x[rt<<1]*x[rt]%(mod-1);//更新指数的值(费马小定理)
        x[rt<<1|1]=1ll*x[rt<<1|1]*x[rt]%(mod-1);
        
        sum[rt<<1]=powmod(sum[rt<<1],x[rt]);//更新结点的值
        sum[rt<<1|1]=powmod(sum[rt<<1|1],x[rt]);
        x[rt]=1;
    }
    if(a[rt]>1){//若底数能进行push_down
        a[rt<<1]=1ll*a[rt<<1]*a[rt]%mod;
        a[rt<<1|1]=1ll*a[rt<<1|1]*a[rt]%mod;//更新底数
        
        sum[rt<<1]=1ll*sum[rt<<1]*powmod(a[rt],ln)%mod;//更新结点的值
        sum[rt<<1|1]=1ll*sum[rt<<1|1]*powmod(a[rt],rn)%mod;
        a[rt]=1;
    }
}
void update1(int L,int R,int l,int r,int rt,int v){//区间更新1,对区间[L,R]都乘上v
    if(L<=l&&R>=r){
        a[rt]=1ll*a[rt]*v%mod;
        sum[rt]=1ll*sum[rt]*powmod(v,(r-l+1))%mod;
        return;
    }
    int mid=(l+r)>>1;
    push_down(rt,mid-l+1,r-mid);
    if(L<=mid) update1(L,R,l,mid,rt<<1,v);
    if(R>mid) update1(L,R,mid+1,r,rt<<1|1,v);
    push_up(rt);
}
void update2(int L,int R,int l,int r,int rt,int k){//区间更新2,使区间[L,R]的值都变为原来的a[i]^k
    if(L<=l&&R>=r){
        a[rt]=1ll*powmod(a[rt],k);
        x[rt]=1ll*x[rt]*k%(mod-1);
        sum[rt]=powmod(sum[rt],k);
        return;
    }
    int mid=(l+r)>>1;
    push_down(rt,mid-l+1,r-mid);
    if(L<=mid) update2(L,R,l,mid,rt<<1,k);
    if(R>mid) update2(L,R,mid+1,r,rt<<1|1,k);
    push_up(rt);
}
ll query(int L,int R,int l,int r,int rt){
    if(L<=l&&R>=r){
        return sum[rt];
    }
    int mid=(l+r)>>1;
    push_down(rt,mid-l+1,r-mid);
    ll res=1ll;
    if(L<=mid) res=1ll*res*query(L,R,l,mid,rt<<1)%mod;
    if(R>mid) res=1ll*res*query(L,R,mid+1,r,rt<<1|1)%mod;
    return res;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        int n,m;
        n=read();
        m=read();
        //scanf("%d%d",&n,&m);
        build(1,n,1);
        while(m--){
            int op,l,r;
            op=read();
            l=read();
            r=read();
            //scanf("%d%d%d",&op,&l,&r);
            if(op==1){
                int v;
                v=read();
                //scanf("%d",&v);
                update1(l,r,1,n,1,v);
            }
            else if(op==2){
                int k;
                k=read();
                //scanf("%d",&k);
                update2(l,r,1,n,1,k);
            }
            else{
                ll res=query(l,r,1,n,1);
                printf("%lld\n",res);
            }
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值