【BZOJ4869】【SHOI2017】相逢是问候

题目大意

  给定一个序列。要求满足区间取 cai c 为定值),区间求和(模p意义下)。 N5104


Solution

  首先看着就像线段树,这种题一般都有一个暴力不会超时的性质。
  对这题来说:
  首先要知道如下欧拉定理EXT:
  

abab mod φ(p) + φ(p)(modp)bφ(p)

  通过不断展开被修改的数,我们可以发现(证明)在一定次数 O(log p) 后便不会再改变。这样只要暴力修改,改到区间都不用修改就跳过就可以了。
   要注意一些地方
  1.据说出题人就漏了这里:计算 φ(p),φ(φ(p)) 时要算到 φ(1)=1 ,而不能只算到 φ(2)=1 。因为若考虑原序列中出现了 0 ,那么在某个φ(p)=2 p 处总有展开次数刚好和展开到φ(2)=1处相同的相邻两项(此处只写了指数)为
  
ccc0 mod p=c(cc0 mod 2 + 2) mod p=c(c mod 2 + 2) mod p

  和
  
cc0 mod p=c mod p

  不一定相等,例如 c=2,p=3 时,上面为 1 ,下面为2;又或 c=2,p=4 时,上面为 0 ,下面为2。错误的原因在于 c2 时, cc02 会被继续展开,而 c0<2 不会展开所以式子的形式不同,不能直接等同。当 0 被其它数x代替时,当且仅当 c=1 会发生,而此时上下式均为 1 mod p=1 ,恒成立。
  那么如果多展开一层,展开后两项(同上)只能是
  
ccc0 mod 2=c(cc0 mod 1 + 1) mod p=c mod p

  和
  
cc0 mod p=c mod p

  故展开后两式相同,结果相等。其余项展开到底后均有
  
...c(c... mod 1+1) mod 2

  的形式。综上所述,只需计算至 φ(1)=1 即可。
  
  2.为了保证 bφ(p) 快速幂加上一些修改判断即可。(听说只要判一层然后可以拿取余后的数接着判,经检验正确率很高QwQ)
  
  

#include<set>
#include<map>
#include<queue>
#include<cmath>
#include<string>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rep(i,a,b) for (int i=a; i<=b; i++)
#define per(i,a,b) for (int i=a; i>=b; i--)
#define debug(x) {cout<<(#x)<<" "<<x<<endl;}
using namespace std;
typedef long long LL;

inline int read() {
    int x=0,f=1; char ch=getchar();
    while (!(ch>='0'&&ch<='9')) {if (ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') {x=x*10+(ch-'0'); ch=getchar();}
    return x*f;
}

const int N = 50005;
const int M = 10005;

int n,m,P,c,k=0;
int a[N],p[N];
bool np[M];
int tot=0,prime[M];
int tr[N<<2],aux[N<<2];

inline void Prep() {
    rep(i,2,M-1) {
        if (!np[i]) prime[++tot]=i;
        rep(j,1,tot) {
            if (i*prime[j]>=M) break;
            np[i*prime[j]]=1;
            if (i%prime[j]==0) break;
        }
    }
}

inline int phi(int x) {
    int ret=x;
    for (int i=1;prime[i]*prime[i]<=x;i++) {
        if (x%prime[i]) continue;
        ret=ret-ret/prime[i];
        while (x%prime[i]==0) x/=prime[i];
    }
    if (x>1) ret=ret-ret/x;
    return ret;
}

inline void pushup(int x) {
    tr[x]=(tr[x<<1]+tr[x<<1|1])%P;
    aux[x]=min(aux[x<<1],aux[x<<1|1]);
}

inline void Build(int x,int l,int r) {
    if (l==r) {
        a[l]=read(); tr[x]=a[l]%P; aux[x]=0; return;
    }
    int mid=(l+r)>>1;
    Build(x<<1,l,mid); Build(x<<1|1,mid+1,r);
    pushup(x);
}

inline int pow(int a,int b,int P,bool &flag) {
    int ret=1;
    bool big=0;
    while (b) {
        if (b&1) {flag|=big|((LL)ret*a>=P); ret=(LL)ret*a%P;}
        if ((LL)a*a>=P) big=1;
        a=(LL)a*a%P; b>>=1;
    }
    return ret;
}

inline int Calc(int dep,int x) {
    int ret=x; if (ret>=p[dep]) ret=ret%p[dep]+p[dep];
    while (dep) {
        dep--;
        bool flag=0;
        ret=pow(c,ret,p[dep],flag);
        if (flag) ret+=p[dep];
    }
    return ret%p[dep];
}

inline void modify(int x,int l,int r,int ll,int rr) {
    if (l>rr||r<ll) return;
    if (aux[x]>=k) return;
    if (l==r) {
        aux[x]++;
        tr[x]=Calc(aux[x],a[l]);
        return;
    }
    int mid=(l+r)>>1;
    modify(x<<1,l,mid,ll,rr); modify(x<<1|1,mid+1,r,ll,rr);
    pushup(x);
}

inline int query(int x,int l,int r,int ll,int rr) {
    if (l>rr||r<ll) return 0;
    if (l>=ll&&r<=rr) return tr[x];
    int mid=(l+r)>>1;
    return (query(x<<1,l,mid,ll,rr)+query(x<<1|1,mid+1,r,ll,rr))%P; 
}

int main() {

    #ifndef ONLINE_JUDGE
        freopen("verbinden.in","r",stdin);
        freopen("verbinden.out","w",stdout);
    #endif

    Prep(); n=read(),m=read(),P=read(),c=read();
    p[0]=P; while (p[k]!=1) {++k;p[k]=phi(p[k-1]);} p[++k]=1; 
    Build(1,1,n); 
    while (m--) {
        int opt=read(),l=read(),r=read();
        if (!opt) modify(1,1,n,l,r);
        else printf("%d\n",query(1,1,n,l,r));
    }

    return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值