一、题目
二、解法
我自己把这道题想出来了哈哈哈(但是考试时候却写锅了嘤嘤嘤
看到我们要求的算式不难联想到欧拉降幂吧,我们先来复习一下欧拉降幂:
a
b
=
{
a
b
m
o
d
φ
(
p
)
+
φ
(
p
)
b
>
φ
(
p
)
a
b
o
t
h
e
r
w
i
s
e
m
o
d
p
a^b=\begin{cases}a^{b\mod\varphi(p)+\varphi(p)}&b>\varphi(p)\\a^b&otherwise\end{cases}\mod p
ab={abmodφ(p)+φ(p)abb>φ(p)otherwisemodp具体实现中我们没上升一个幂的位次就要将
p
p
p变成
φ
(
p
)
\varphi(p)
φ(p),当模数是
1
1
1的时候就特别好算了,而
φ
\varphi
φ的下降到
1
1
1是
log
\log
log级别的,所以也能推出当算式“高度”满足了让模数下降到
1
1
1时,这个算式的值就不会再改变了,每个位置就只会被修改
log
\log
log次,而每次修改(直接欧拉降幂)是
log
2
\log^2
log2的,所以这样做的总时间复杂度是
O
(
n
log
3
n
)
O(n\log ^3n)
O(nlog3n)
讲一下具体实现的细节,用并查集优化我们的暴力修改,如果一个位置的算式已经不会改变了,那我们把这个位置的并查集并到下一个位置, i + 1 i+1 i+1时找到他的祖宗,那么就可以达到加速访问的效果了。第二个点就是我们要预处理可能用到的 φ \varphi φ。第三个点就是用通过预处理使得我们能 O ( 1 ) O(1) O(1)算 c b = c b % 10000 × c 10000 ( b / 10000 ) c^b=c^{b\%10000}\times c^{10000(b/10000)} cb=cb%10000×c10000(b/10000),这样就可以省掉一个 log n \log n logn。
最后讲一下我考试时候自己写遇到的 4 4 4个锅:
- 锅 1 1 1:判断一个数是否不会改变的时候我的 k k k(多少次降到 1 1 1)错打成了 m m m(询问组数)
- 锅 2 2 2:输出答案的时候没有处理好模数,养成习惯,要先模再加再模。
- 锅 3 3 3,注意 φ = 1 \varphi=1 φ=1的时候还要判断被模数,如果不等于 0 0 0返回 1 1 1,否则返回 0 0 0(严格遵循公式)
- 锅 4 4 4,注意判断一个算式不会改变应该到 k + 1 k+1 k+1,因为要保证每个模数都对应的是 c c c(我是从 0 0 0开始存的, p h i [ 0 ] = p phi[0]=p phi[0]=p,所以同一个地方出了两个锅)
最后贴上代码,作者懒,写的
O
(
n
log
3
n
)
O(n\log^3 n)
O(nlog3n),
l
o
j
loj
loj上过了,但是改起来应该不麻烦
#include <cstdio>
const int M = 50005;
#define int long long
int read()
{
int num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int n,m,k,p,c,a[M],b[M],val[M],s[M],ph[M],fa[M];
int find(int x)
{
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
int phi(int n)
{
int r=n;
for(int i=2;i*i<=n;i++)
if(n%i==0)
{
r=r/i*(i-1);
while(n%i==0) n/=i;
}
if(n>1) r=r/n*(n-1);
return r;
}
void init()
{
ph[0]=p;
for(int i=1;;i++)
{
ph[i]=phi(ph[i-1]);
if(ph[i]==1)
{
k=i;
break;
}
}
for(int i=1;i<=n+1;i++)
fa[i]=i;
}
int lowbit(int x)
{
return x&(-x);
}
void upd(int x,int f)
{
for(;x<=n;x+=lowbit(x))
b[x]=(b[x]+f)%p;
}
int ask(int x)
{
int r=0;
for(;x>=1;x-=lowbit(x))
r=(r+b[x])%p;
return r;
}
int qkpow(int a,int b,int p)
{
int r=1;
while(b>0)
{
if(b&1)
{
r=r*a;
if(r>p) r=r%p+p;
}
a=a*a;
if(a>p) a=a%p+p;
b>>=1;
}
return r;
}
int work(int x,int p)//哪个下标,第几层
{
if(p==k)
{
if(p==s[x])
{
if(a[x]==0) return 0;
return 1;
}
if(c==0) return 0;
return 1;//模1 锅3
}
if(p==s[x]) return a[x];//到了a[x]
return qkpow(c,work(x,p+1),ph[p]);
}
signed main()
{
n=read();m=read();p=read();c=read();
init();
for(int i=1;i<=n;i++)
{
a[i]=val[i]=read();
upd(i,a[i]);
}
while(m--)
{
int op=read(),l=read(),r=read();
if(op==0)
{
for(int i=find(l);i<=r;i=find(i+1))
{
upd(i,-val[i]);
s[i]++;//有多少个c
val[i]=work(i,0);
upd(i,val[i]);
if(s[i]==k+1) fa[i]=find(i+1);//锅1、锅4
}
}
else
{
printf("%lld\n",((ask(r)-ask(l-1))%p+p)%p);//锅2
}
}
}
/*
下面是我考试时候写的注释:
感觉我会了
利用phi快速下降的性质!
真是数学考试 哈哈哈
时间复杂度O(nlog^3 n)
所以写一个欧拉降幂+树状数组+并查集
*/