测试地址:相逢是问候
做法:本题需要用到扩展欧拉定理+线段树。
我们知道在时,有欧拉定理:
然而本题中并不一定互质,那么我们有扩展欧拉定理:
当时,
至于定理的证明,这里写不下(实际上是我不会),网上有很多大佬证过,可以去找一找。
那么我们有了的求法,那呢?
注意到有
我们发现右边上面的又可以用扩展欧拉定理来算,于是如果我们知道这个东西嵌套几层,我们就可以递归去算了。
观察到,模数随着层数的增加,每增加一层都取一次。有一个结论是,一个数不断取,最多取次就会变成。简单证明一下这个结论,我们知道求的过程就是先把该数质因数分解,然后对于每种质因子,把其中一个变成。我们知道,除了之外所有质数都是奇数,那么就一定是偶数,所以新的数一定会多出一些质因子,而与此同时,我们每次都把一个变成,也就是整个数除以,至此我们证明了除了第一次外(因为一开始可能没有),每次数字大小都会减少至少一半,所以结论得证。
那么我们只需要知道在什么时候模数变成,那么无论内层再怎么算,结果都是,也就不再有影响了。于是我们用线段树维护每个数距离不变还剩的操作次数,如果一个区间内所有的数都不会再变,就不往下查找,否则暴力从每个点向根节点进行修改,时间复杂度为(这个时间复杂度分析和区间取模的复杂度分析差不多啊…)。再加上每次用扩展欧拉定理修改,每次修改是的,那么总的时间复杂度为,虽然常数要小很多,BZOJ上也可以过,但分点测试仍然会挂。
注意到修改的的复杂度中,有一个是因为扩展欧拉定理的递归,另一个是因为快速幂。扩展欧拉定理肯定没法优化了,所以我们从快速幂的角度进行优化。因为底数始终是,我们可以令,那么我们只需预处理出和两个表,即可做到询问。注意要对每种模数都做一次这个表,那么预处理复杂度为,前面的算法时间复杂度优化为,可以飞快地通过此题。
这题还有一个比较难考虑到的点:计算每个数距离不变还剩的操作次数时,不能简单的用取一直取到的次数来做,而是应该,因为如果序列中存在,那么例如当时,它只需要次取就能变成,而当时,需要操作次才能进入不变的状态,这显然就不对了。所以应该使用取的次数来作为最大操作次数。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,tot,mx[200010];
ll p,c,a[50010],seg[200010],Phi[50],pwr[10010][50],pwrx[10010][50],low[50];
ll phi(ll x)
{
ll s=x;
for(ll i=2;i*i<=x;i++)
if (x%i==0)
{
s=s/i*(i-1);
while(x%i==0) x/=i;
}
if (x!=1) s=s/x*(x-1);
return s;
}
void pushup(int no)
{
seg[no]=(seg[no<<1]+seg[no<<1|1])%p;
mx[no]=max(mx[no<<1],mx[no<<1|1]);
}
void buildtree(int no,int l,int r)
{
if (l==r)
{
scanf("%lld",&a[l]);
seg[no]=a[l];
mx[no]=tot;
return;
}
int mid=(l+r)>>1;
buildtree(no<<1,l,mid);
buildtree(no<<1|1,mid+1,r);
pushup(no);
}
ll power(ll b,int p)
{
if (b<10000) return pwr[b][p];
else return pwrx[b/10000][p]*pwr[b%10000][p]%Phi[p];
}
ll euler(ll x,ll t)
{
ll last,tmp=x;
if (tmp>Phi[t]) tmp=tmp%Phi[t]+Phi[t];
for(int i=t;i>0;i--)
{
last=tmp;
tmp=power(tmp,i-1);
if (last>=low[i-1]) tmp+=Phi[i-1];
}
return tmp;
}
void modify(int no,int l,int r,int s,int t)
{
if (!mx[no]) return;
if (l==r)
{
mx[no]--;
seg[no]=euler(a[l],tot-mx[no]);
return;
}
int mid=(l+r)>>1;
if (s<=mid&&mx[no<<1]) modify(no<<1,l,mid,s,t);
if (t>mid&&mx[no<<1|1]) modify(no<<1|1,mid+1,r,s,t);
pushup(no);
}
ll query(int no,int l,int r,int s,int t)
{
if (l>=s&&r<=t) return seg[no]%p;
ll sum=0;
int mid=(l+r)>>1;
if (s<=mid) sum=(sum+query(no<<1,l,mid,s,t))%p;
if (t>mid) sum=(sum+query(no<<1|1,mid+1,r,s,t))%p;
return sum;
}
int main()
{
scanf("%d%d%lld%lld",&n,&m,&p,&c);
tot=0;
ll x=p;
Phi[0]=x;
while(x!=1)
{
x=phi(x);
Phi[++tot]=x;
}
Phi[++tot]=1;
for(int i=0;i<=tot;i++)
{
pwr[0][i]=1%Phi[i];
low[i]=0;
for(int j=1;j<=10000;j++)
{
pwr[j][i]=pwr[j-1][i]*c;
if (pwr[j][i]>=Phi[i]&&!low[i]) low[i]=j;
pwr[j][i]%=Phi[i];
}
pwrx[1][i]=pwr[10000][i];
for(int j=2;j<=10000;j++)
pwrx[j][i]=pwrx[j-1][i]*pwr[10000][i]%Phi[i];
}
buildtree(1,1,n);
for(int i=1;i<=m;i++)
{
int op,l,r;
scanf("%d%d%d",&op,&l,&r);
if (!op) modify(1,1,n,l,r);
else printf("%lld
",query(1,1,n,l,r));
}
return 0;
}