题解 P3934 [Ynoi2016] 炸脖龙 I
0.前言
关于这个蒟蒻与SB(SB:是小黑不是SB)的差距
以及是这篇博客中的高档题三字才有了这篇题解。
update 2022.11.22:修改了一些之前不知道的专有名称
update 2022.11.23:修改了一些错误
1.正文
不要轻易尝试原题面中的gal,有这时间不如去学习珂学
前置芝士:线性筛,欧拉函数,扩展欧拉定理,线段树,树状数组人傻常数大患者特供
亿眼看过去,发现操作2是不具有区间可加性的
所以考虑暴力枚举
看一眼式子
发现这个式子具有递归性
于是在计算原式之前,先计算\(a[l+1]^{a[l+2]^{\dots ^{a[r]}}}\),令这个值为\(T\),再计算\(a[l]^T\)。然后发现\(T\)可以以相同方式计算
当然我们不可能真的去计算这么大的\(T\),因此要对\(T\)进行降幂(这里的降幂叫欧拉降幂),一般情况下用欧拉定理。但是这里\(a\)与\(p\)不一定互质,因此要用扩展欧拉定理,提前用线性筛筛出\(\varphi\)
然后本蒟蒻就靠着暴力拿了90分
仔细思考一下,会发现暴力其实没有这么暴力,相反,它是一个
\(O(\log n)\)的优秀复杂度
考虑\(a\)与\(\varphi(a)\)的关系
- \(a\)是奇数,那么\(\varphi(a)\)必是偶数(与\(a\)互质的数必然是成对出现)
- \(a\)是偶数,那么\(\varphi(a)\le\frac{a}{2}\)(\(1\)~\(a\)有\(\frac{a}{2}\)个偶数)
又因为\(1^k=1\),\(\varphi(2)=\varphi(1)=1\),因此模数为\(1\)时直接返回\(0\)即可(任何数模\(1\)均为\(0\)),递归调用的时间复杂度是\(O(\log n)\)
但扩展欧拉定理是有适用范围的
所以要对\(b\)进行分类讨论
问题来了,如何快速判断\(b\)与\(\varphi(m)\)的关系?
注意到类似\(a[l]^{a[l+1]^{a[l+2]^{\dots ^{a[r]}}}}\)的式子增长速度非常快(毕竟\(O(a^n)\))的复杂度已经足够恐怖了)
所以往上递归常数次就应该会直接超出\(\varphi(m)\)
用calc计算得到
所以只要连续的大于\(1\)数超过\(5\)个那就一定大于\(\varphi(m)\),否则直接暴力计算
code
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
const int MAXN=2e7+11,maxn=5e5+10;
ll tree[maxn],a[maxn],n,m;//人傻常数大
int prime[MAXN],v[MAXN],cnt,phi[MAXN];
//-------------筛φ
void qiu_prime(){
phi[1]=1;
for(int i=2;i<=2e7;i++){
if(!v[i]) v[i]=i,prime[++cnt]=i,phi[i]=i-1;
for(int j=1;j<=cnt;j++){
if(prime[j]>v[i]||prime[j]*i>2e7) break;
v[i*prime[j]]=prime[j];
phi[i*prime[j]]=phi[i]*(i%prime[j]?prime[j]-1:prime[j]);
}
}
}
//------------树状数组
/*
这里不是直接在原序列上进行差分,而是
参考了蓝书《算法竞赛进阶指南》的写法,
另开一个b数组维护每次区间修改的影响,
查询时求出b数组的前缀和后在加上原序列中的值
*/
int lowbit(int x){return x&(-x);}
void update(int l,int r,ll x){
for(int i=l;i<=n;i+=lowbit(i)){
tree[i]+=x;
}
for(int i=r+1;i<=n;i+=lowbit(i)){
tree[i]-=x;
}
}
ll Find(int x){
ll ans=0;
for(int i=x;i;i-=lowbit(i)){
ans+=tree[i];
}
ans+=a[x];
return ans;
}
//------------快速幂
ll qpow(ll x,ll y,ll p){
ll res=1;
x%=p;
while(y){
if(y&1) res=res*x%p;
x=x*x%p;
y>>=1;
}
return res;
}
//------------维护幂塔
ll solve(int L,int R,int p){
if(L==R+1||p==1) return 1;//(1)
if(Find(L)%p==0) return 0;//底数是模数的倍数时值一定为0
ll y=min(L+5,R);//确定范围,如果总长度小于5就尴尬了
ll last=y;//确定最后一个大于1的数出现的位置
for(ll i=L+1;i<=y;i++){
if(Find(i)==1){
last=i-1;
break;
}
}
ll times=1,g=1;//times就是指数,g就是当前的值
for(ll i=last;i>=L+1;i--){
//注意!幂运算是从上往下递归进行的!所以从最后一位开始枚举
//并且Find(L)作为底数,所以枚举到L+1即可
times=g,g=1;//根据原式的递归计算可以得到上一个底数求出的值是当前这个底数的指数
while(times--){
g=g*Find(i);
if(g>=phi[p]){//如果大于等于φ(p)直接用扩展欧拉定理
return qpow(Find(L),solve(L+1,R,phi[p])+phi[p],p);
}
}
}
return qpow(Find(L),g,p);//不满足就直接暴力计算
}
int main(){
ios::sync_with_stdio(false);
qiu_prime();
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=m;i++){
int tag;
cin>>tag;
if(tag==1){
ll L,R,x;
cin>>L>>R>>x;
update(L,R,x);
}
else{
ll L,R,p;
cin>>L>>R>>p;
cout<<solve(L,R,p)%p<<endl;//模p可以避免模数一开始就为1的情况
}
}
return 0;
}
看到代码中的(1)处了吗?
我在实际提交的过程中发现一个问题:当\(p=1\)时,无论是返回\(0\)还是返回\(1\)都是正确的!
但按照我们刚才的定义,模数为\(1\)时应该返回\(0\)才对。
思考一下,\(\varphi\)的值只有在\(2\)和\(1\)时才能取到\(1\)
\(\varphi\)为\(1\)时显然无需考虑
\(\varphi\)位\(2\)时原式应该长这样
此时我们发现,原式的值与指数多少无关,只与\(a\)的值有关
因此无论返回值是\(0\)还是\(1\)均可
(事实上,根本没有必要针对\(p\)设定一个返回值,因为当\(p=1\)时底数一定是\(p\)的倍数,可以直接进入优化条件)
我看到很多题解中都没有提到过这个问题,选\(0\)或\(1\)也没有真正说清楚,更像是随意为之。如果有疑问或者反驳欢迎来爆踩我这个蒟蒻
2.尾声
题解到这里就结束啦!
我永远喜欢珂朵莉和奈芙莲(被双方爆锤)
本文来自博客园,作者:cmd_pig,转载请注明原文链接:https://www.cnblogs.com/cmd-pig/articles/16599806.html