考虑一个欧拉定理的扩展:
当 x>φ(p) 时有:
cx≡cx%φ(p)+φ(p)(modp)
也叫作欧拉定理EXT,证明看这里:https://zhuanlan.zhihu.com/p/24902174
这个公式的强大之处在于a和p可以不互质,也就是说p可以不为质数。
然后证明一个结论:这个操作在操作 logP 次之后一定是一个定值。
显然可以发现一个数 P 在最多取 O(logP) 次欧拉函数之后会变成1,(一个简单的证明:奇数变偶数)。
然后就可以发现在不断地套用欧拉定理的时候,指数模 φ(p) ,指数的指数模 φ(φ(p)) …………最多 O(logP) 次之后变成模1加1,也就是1,之后不管有多少指数都会变成1,所以一个数在操作 O(logP) 次之后再操作就没有意义了。(和http://uoj.ac/problem/228很像,都是操作一定次数之后不变)
考虑使用线段树来做,如果当前区间全部已经不再变化就退出,否则暴力修改下去。所以最坏情况下每个数会修改 O(logP) 次,每次修改会修改树上 O(logN) 个节点,所以时间复杂度是 O(NlogNlogP) 。
但是在这里我们默认了修改一次的时间是 O(1) ,然而事实并非如此。对于一个数的快速幂,需要递归 O(logP) 层(因为每层指数的模数都是不一样的,都要重新算),所以一个数快速幂就要两个log,一共有 nlogP 个数(对不是n个数, x、cx、ccx…… 这些都要重新算的,我开始就是这里少算了一个log),所以快速幂的预处理就变成了三个log!虽然开了O2,但是如果常数爆炸的话很有可能被卡掉。
接着我们尝试优化掉一个log:计算快速幂的那个log。为什么可以优化掉呢?因为底数一定是c,而指数最大是1e8,那么我们可以预处理 cx 和 tx ,其中 t=c10000,x<=10000 ,对于任何一个幂,指数大于10000的部分从 tx 的表中找,小于10000的部分从 cx 的表中找,这样总的时间复杂度降到两个log。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
#define inf 1000000000
#define N 50005
#define ls x<<1
#define rs x<<1|1
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
int sum[N*4],num[N*4],tag[N*4],a[N];
int f[N][100],cc[N][100],ex_c[N][100],pp[100];
int n,m,p,c,k,i,opt,l,r;
int quick_power(int x,int a,int mo)
{
int res = 1;
while (a) {if (a&1) res=(1ll*res*x)%mo; x=(1ll*x*x)%mo; a>>=1;}
return res;
}
void pushup(int x) {sum[x] = (sum[ls] + sum[rs]) % p; tag[x] = tag[ls] + tag[rs];}
void build(int x,int l,int r)
{
if (l == r) {sum[x] = a[l]; num[x] = 0; tag[x] = r - l + 1; return;}
int mid = (l + r) >> 1; build(ls,l,mid); build(rs,mid+1,r);
pushup(x);
}
void change(int x,int l,int r,int L,int R)
{
if (L <= l && r <= R && !tag[x]) return;
if (l == r)
{
num[x]++; sum[x] = f[l][num[x]];
if (num[x] == k) tag[x]--;
return;
}
int mid = (l + r) >> 1;
if (L <= mid) change(ls,l,mid,L,R); if (mid < R) change(rs,mid+1,r,L,R);
pushup(x);
}
int query(int x,int l,int r,int L,int R)
{
if (L <= l && r <= R) return sum[x];
int mid = (l + r) >> 1; ll res = 0;
if (L <= mid) res += query(ls,l,mid,L,R);
if (mid < R) res += query(rs,mid+1,r,L,R);
return res % p;
}
int phi(int x)
{
int i,res = x;
fo(i,2,sqrt(x)) if (!(x%i)) {while (!(x%i)) x/=i;res = res/i*(i-1);}
if (x-1) res=res/x*(x-1);
return res;
}
int q_p(int x,int a,int mm)
{
if (a <= 10000) return cc[a][mm];
return (1ll*ex_c[a/10000][mm]*cc[a%10000][mm])%pp[mm];
}
int get_(int x,int num,int d)
{
int t;
if (!num) if (x > pp[d]) return x%pp[d]; else return x;
t = get_(x,num-1,d+1);
double q = log(1.0/x*pp[d+1])/log(1.0*c)-num+1;
if (q<=0) t += pp[d+1];
return q_p(c,t,d);
}
void pre()
{
int i,j;
fo(i,0,10000)
fo(j,0,k)
{
cc[i][j] = quick_power(c,i,pp[j]);
int g = quick_power(c,10000,pp[j]);
ex_c[i][j] = quick_power(g,i,pp[j]);
}
fo(i,1,n)
if (a[i] == 0)
{
f[i][1] = 1;
fo(j,2,k)
f[i][j] = get_(1,j-1,0);
} else
{
fo(j,1,k)
f[i][j] = get_(a[i],j,0);
}
}
int main()
{
scanf("%d%d%d%d",&n,&m,&p,&c);
k = 0; pp[0] = p;
while (pp[k]-1) {k++; pp[k] = phi(pp[k-1]);}
k++; pp[k] = 1;
fo(i,1,n) scanf("%d",&a[i]);
pre();
build(1,1,n);
while (m--)
{
scanf("%d%d%d",&opt,&l,&r);
if (opt == 0) change(1,1,n,l,r);
if (opt == 1) printf("%d\n",query(1,1,n,l,r));
}
//fo(i,l,r) printf("%d ",query(1,1,n,i,i));
return 0;
}