题解 P3934 【Nephren Ruq Insania】
这个题的思路大佬们都介绍的差不多了,但是他们的代码真的好长,我介绍一种对于萌新很友好的代码。
前置知识:1. 扩展欧拉定理
注意扩展欧拉定理的使用条件是模数大于等于欧拉函数值
2.欧拉函数通项公式:
3.欧拉函数是积性函数:
注意,
#include<iostream>
#include<cstdio>
#include<map>
using namespace std;
typedef long long ll;
ll tree[2000000];
ll n,m,p,prime[5000001],cnt,phi[20000001];
bool bj[20000001];
inline ll read(){//快读
ll x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-')f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
void eular()
{//线性筛欧拉函数
phi[1]=1;bj[1]=1;//特殊处理1的情况
for(register ll i=2;i<=20000000;i++)
{
if(!bj[i])//之前没被筛掉说明是指数
{
prime[++cnt]=i;
phi[i]=i-1;//质数和前面所有数互质,欧拉函数值位i-1
}
for(ll j=1;j<=cnt&&prime[j]*i<=20000000;j++)
{//尝试用 prime[j]*i 去筛掉其他的数字
bj[prime[j]*i]=1;//是合数,标记上
if(i%prime[j]==0)//欧拉筛的核心思想:让一个数字只被它的最小质因子筛一次
{//prime是由小到大的,如果j不是i的最小质因子,那么i应该会在之前就break了
//之后就不用再拿i去筛了,因为下次筛到的数的最小质因子一定是i的最小质因子
phi[i*prime[j]]=phi[i]*prime[j];
break;//欧拉函数的通项公式,注意这里不是积性函数部分,i已经包含prime[j],i*prime[j]的phi就是比i的prime[j]倍
}
phi[i*prime[j]]=phi[i]*phi[prime[j]];//i,prime[j]互质,利用phi的积性乘起来就好了
}
}
}//区间加、查询单点,使用简短的树状数组维护
ll lowbit(ll x){return (x&(-x));
void updata(ll x,ll cost){
while(x<=n){
tree[x]+=cost;
x+=lowbit(x);
}//把x位加上cost
}
ll query(ll x){//查询tree数组的前缀和1-x
ll ans=0;
while(x>0){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
ll fastpow(ll di,ll zhi,ll p)
{
ll flag=0,yu=1;
if(di>=p)flag=1,di%=p;//小心,去掉这句60分,否则如果有0次方的情况就去世了
//数据本身不会出现0次方的情况,但是别忘了我们的fastpow是取过模的,谁知道会不会有0
while(zhi>0)
{
if(zhi%2==1)yu=yu*di;
di=di*di;//这里和普通的快速幂不同 ,先不要对di和yu取模,
//因为扩展欧拉定理的使用需要指数大于模数的欧拉函数值,而在我们递归调用solve的时候
//fastpow()就是上一层solve的指数!(它的p是上一层的欧拉函数值)所以要判断是否能达到p。
if(yu>=p)flag=1,yu%=p;//如果达到了p就标记
if(di>=p)flag=1,di%=p;
zhi=zhi/2;
}
if(flag)yu+=p;//如果到达p,根据扩展欧拉定理加上p
return yu;
}
//看似是最坏O(n)的dfs,其实最多执行log层
//对一个数字不停求欧拉函数,求logn次就会变成1
//例如phi(100)->phi(40)->phi(16)->phi(8)->phi(4)-phi>(2)->1
//因为gcd(i,n)=gcd(n-i,n),任意n>2,phi(n)为偶数
//偶数一定含有素因子2,根据欧拉函数通项公式,可以发现每次求欧拉函数都是至少折半的
//扩展欧拉定理,不知道的建议取做 欧拉定理 那个题目
ll Query(ll l,ll r,ll p)
{//做这个题目还可以顺手切了Power Tower,把树状数组改成正常数组就好了
ll now=query(l);
if(l==r||p==1)return now>=p?now%p+p:now;//注意边界有两个:p已经降到了1或者序列已经取完,还是判断w[l]和p的大小关系
return fastpow(now,Query(l+1,r,phi[p]),p); //递归调用solve,注意不要取余数,这可能会导致fastpow里最后加上的p白加了
}
int main()
{
eular();
n=read();m=read();
for(ll i=1;i<=n;i++)
{
ll x=read();
updata(i,x);//维护差分原数组的差分,修改一个区间值只会影响l、r+1的差分值
//这样query求出的tree的前缀和就是单点值,实现了区间加、求单点
updata(i+1,-x);
}
for(ll i=1;i<=m;i++)
{
ll opt,x,y,z;
opt=read();x=read();y=read();z=read();
if(opt==1)
{
updata(x,z);//维护差分序列
updata(y+1,-z);
}
else printf("%lld\n",Query(x,y,z)%z);//注意这里还要%p,因为fastpow()最后一次执行的时候可能加上了P,而solve里是不能取余数的
}
return 0;
}