BZOJ4869: [Shoi2017]相逢是问候-线段树+数论

传送门

题意:

给出一个序列a,两种操作

1.将[l,r]这段区间所有的数 ai a i 换为 cai c a i

2.求[l,r]这段区间的和,对p取模

1n50000; 1m50000; 1p100000000; 0<c<p; 0ai<p 1 ≤ n ≤ 50000 ;   1 ≤ m ≤ 50000 ;   1 ≤ p ≤ 100000000 ;   0 < c < p ;   0 ≤ a i < p

Solution:

这道题有些类似于BZOJ3884Codeforces906D

对一个位置执行多次1操作后,φ的值就变成了1,这个位置的值就不变了,可以证明执行操作的次数是log级别的,所以我们建一棵线段树,加一个标记表示这个节点表示的区间是否还会发生变化,不能就跳过,能就在里面暴力赋值,对于每个点先预处理出这个点从初始到不变的所有可能变成的值,最后线段树维护即可

维护的复杂度是 O(nlog2n) O ( n log 2 ⁡ n ) ,预处理的复杂度是 O(nlog3n) O ( n log 3 ⁡ n )

然后发现T掉了….

怎么办呢?

发现p的值是1e8,且每次快速幂的指数是一定的,所以说我们可以通过预处理c的不同次幂来优化掉快速幂的log

然后这道题就可以愉快的A掉啦(雾

代码:

#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
int n,m,mod,c;
const int N=50010; 
int a[N],f[N][30],st[30],cnt;
struct tree{
    int l,r,num,len;
    int v; 
}tr[4*N];
int bq[10010][30],sq[10010][30];
int phi(int x)
{
    int t=sqrt(x),ans=x;
    for (int i=2;i<=t;i++)
    {
        if (x%i==0)
        {
            ans-=ans/i;
            while (x%i==0) x/=i;
        }
    }
    if (x>1) ans-=ans/x;
    return ans; 
}
int fast_pow(int a,int x,int mod)
{
    int ans=1;
    for (;x;x>>=1,a=1ll*a*a>=mod?1ll*a*a%mod+mod:1ll*a*a%mod)
        if (x&1) ans=1ll*a*ans>=mod?1ll*a*ans%mod+mod:1ll*a*ans%mod;
    return ans;
}
int fpow(int a,int x,int pos)
{
    int mod=st[pos];
    long long t=1ll*bq[x/10000][pos]*sq[x%10000][pos];
    return t>=mod?t%mod+mod:t%mod;
} 
int dfs(int i,int l,int r)
{
    if (st[l]==1) return 1;
    if (l==r) return a[i]<st[l]?a[i]:a[i]%st[l]+st[l];
    int T=dfs(i,l+1,r);
    return fpow(c,T,l);
}
void update(int i)
{
    tr[i].v=(tr[i<<1].v+tr[i<<1|1].v)%mod;
    tr[i].len=tr[i<<1].len+tr[i<<1|1].len;
}
void build(int i,int l,int r)
{
    tr[i].l=l,tr[i].r=r;
    if (l==r) {tr[i].len=1;tr[i].v=a[l];return;}
    int mid=l+r>>1;
    build(i<<1,l,mid);build(i<<1|1,mid+1,r);
    update(i);
}
void modify(int i,int l,int r)
{
    if (tr[i].len==0) return;
    int L=tr[i].l,R=tr[i].r;
    if (L>r||l>R) return;
    if (L==R){tr[i].num++;tr[i].v=f[L][tr[i].num];if (tr[i].num==cnt) tr[i].len--;return;} 
    modify(i<<1,l,r);modify(i<<1|1,l,r);
    update(i);
}
int query(int i,int l,int r)
{
    int L=tr[i].l,R=tr[i].r;
    if (L>r||l>R) return 0;
    if (l<=L&&R<=r) return tr[i].v; 
    return (query(i<<1,l,r)+query(i<<1|1,l,r))%mod;
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&mod,&c);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    st[0]=mod;
    while (st[cnt]-1) cnt++,st[cnt]=phi(st[cnt-1]);
    st[++cnt]=1;
    for (int i=0;i<=cnt;i++)
    {
        for (int j=0;j<=10000;j++)
            bq[j][i]=fast_pow(c,j*10000,st[i]);
        for (int j=0;j<=10000;j++)
            sq[j][i]=fast_pow(c,j,st[i]);
    }
    for (int i=1;i<=n;i++)
    {
        if (a[i]==0) {f[i][1]=1;a[i]=1;for (int j=2;j<=cnt;j++)f[i][j]=dfs(i,0,j-1)%mod;a[i]=0;}
        else
        {
            for (int j=1;j<=cnt;j++)
                f[i][j]=dfs(i,0,j)%mod;
        }
    }
    build(1,1,n);
    for (int p,x,y,i=1;i<=m;i++)
    {
        scanf("%d%d%d",&p,&x,&y);
        if (!p) modify(1,x,y);
        else printf("%d\n",query(1,x,y));
    }
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值