[题解]bzoj4869 SHOI2017相逢是问候

29 篇文章 0 订阅
17 篇文章 0 订阅

Description

Informatikverbindetdichundmich.
信息将你我连结。B君希望以维护一个长度为n的数组,这个数组的下标为从1到n的正整数。一共有m个操作,可以
分为两种:0 l r表示将第l个到第r个数(al,al+1,…,ar)中的每一个数ai替换为c^ai,即c的ai次方,其中c是
输入的一个常数,也就是执行赋值ai=c^ai1 l r求第l个到第r个数的和,也就是输出:sigma(ai),l<=i<=rai因为
这个结果可能会很大,所以你只需要输出结果mod p的值即可。

Input

第一行有三个整数n,m,p,c,所有整数含义见问题描述。
接下来一行n个整数,表示a数组的初始值。
接下来m行,每行三个整数,其中第一个整数表示了操作的类型。
如果是0的话,表示这是一个修改操作,操作的参数为l,r。
如果是1的话,表示这是一个询问操作,操作的参数为l,r。
1 ≤ n ≤ 50000, 1 ≤ m ≤ 50000, 1 ≤ p ≤ 100000000, 0 < c

Output

对于每个询问操作,输出一行,包括一个整数表示答案mod p的值。

Sample Input

4 4 7 2
1 2 3 4
0 1 4
1 2 4
0 1 4
1 1 3

Sample Output

0
3

Solution

线段树。可以看得出操作有限次之后不会再变化,因为我们有扩展欧拉定理: cxcx mod ϕ(p)+ϕ(p) mod p,x>ϕ(p)
所以最多 O(logP) 次指数就会变为模1加1,所以我们用线段树暴力下去修改,同时记下每个区间的修改次数,如果整个区间内的修改次数超过限制就直接返回,加上快速幂的复杂度,这是三个log的算法,但是过得去。

#include<cstdio>
#include<algorithm>
using namespace std;

template<typename T>inline void read(T &x){
    T f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
    for(x=0;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
    x*=f;
}

typedef long long LL;
const int maxn=50010;
int n,m,a[maxn],cnt,c;
LL mod[maxn];

LL phi(LL x){
    LL res=x;
    for(int i=2;i*i<=x;i++){
        if(!(x%i)){
            res=res*(i-1)/i;
            while(!(x%i))x/=i;
        }
    }
    if(x!=1)res=res*(x-1)/x;
    return res;
}
LL pow(LL x,LL y,LL modnum){
    LL res=1;
    while(y){
        if(y&1)res=res*x%modnum;
        y>>=1;x=x*x%modnum;
    }
    return res;
}
LL Calc(LL x,int t){
    LL res=x;
    if(res>mod[t])res=res%mod[t]+mod[t];
    while(t--){
        res=pow(c,res,mod[t]);
        if(!res)res=mod[t];
    }
    return res%mod[0];
}
struct Segment_Tree{
    #define lc x<<1
    #define rc x<<1|1
    int L[maxn<<2],R[maxn<<2];
    LL sum[maxn<<2],dep[maxn<<2];
    void Build(int l,int r,int x=1){
        if((L[x]=l)==(R[x]=r)){
            dep[x]=0;sum[x]=a[l];
            return;
        }
        int mid=(l+r)>>1;
        Build(l,mid,lc);Build(mid+1,r,rc);
        dep[x]=min(dep[lc],dep[rc]);
        sum[x]=(sum[lc]+sum[rc])%mod[0];
    }
    void Modify(int l,int r,int x=1){
        if(R[x]<l||L[x]>r||dep[x]>=cnt)return;
        if(L[x]==R[x])return sum[x]=Calc(a[L[x]],++dep[x]),void();
        Modify(l,r,lc);Modify(l,r,rc);
        dep[x]=min(dep[lc],dep[rc]);
        sum[x]=(sum[lc]+sum[rc])%mod[0];
    }
    LL Query(int l,int r,int x=1){
        if(R[x]<l||L[x]>r)return 0;
        if(L[x]>=l&&R[x]<=r)return sum[x];
        return (Query(l,r,lc)+Query(l,r,rc))%mod[0];
    }
}tree;

int main(){
    read(n);read(m);read(mod[0]);read(c);
    while(mod[cnt]!=1)cnt++,mod[cnt]=phi(mod[cnt-1]);
    mod[++cnt]=1;
    for(int i=1;i<=n;i++)read(a[i]);
    tree.Build(1,n);
    while(m--){
        int opt,l,r;
        read(opt);read(l);read(r);
        if(!opt)tree.Modify(l,r);
        else printf("%lld\n",tree.Query(l,r));
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值