[六省聯考2017] 相逢是問候
題意:
給定一個長度為n的序列A,常數p和c。你需要支援m次操作,分為兩種:
- 0 l r:將區間$[l,r]$中所有$a_{i}$替換成$c^{a_{i}}$。
- 1 l r:查詢區間$[l,r]$中所有數的和,對p取模。
$n,m\leq 50000,c<p\leq 10^{8}$。
題解:
一般像這種看起來根本沒法維護的線段樹題都是操作幾次就變常數了,於是我們考慮一下這個操作的性質。
首先引入拓展尤拉定理:
$a^{b}\ mod\ p=\begin{cases}a^{b},b<\phi(p) \\ a^{b\ mod\ \phi(p)+\phi(p)},b\geq \phi(p) \\ \end{cases}$
注意到$\phi^{k}(p)$在$k=30$左右的時候就會變成1,而任何數對1取模都等於0。
也就是說,當某個位置i被操作k次之後,它初始的$a_{i}$會因為對1取模而消失掉。
那麼當一個區間被操作k次之後,它包含的所有$a_{i}$都是同一個值,且無論再操作多少次都還是這個值。
所以我們只需要預處理出每個位置操作k次之內的答案,對於一次修改,我們暴力遞迴修改次數小於k的區間即可。
(注意不能對於一次查詢暴力遞迴,可能會出現沒有修改只有查詢的情況)
每個區間最多被暴力更新k次,所以線段樹的複雜度不會超過$O(n\log{n}k)$,其中k是$\log{p}$級別的。
但是預處理複雜度是$O(nk^{3})$的,瓶頸在於快速冪。
這裡面有一個小技巧:利用BSGS的思想預處理出$0\leq i<10000,c^{i}$的值和$0\leq i<10000,c^{10000i}$的值,兩邊拼一下即可。
在快速冪裡還需要判斷一下指數b是否小於$\phi(p)$,注意到$2^{2^{2^{2^{2}}}}$肯定已經超過p了,所以只在5以內判一下即可,不會爆longlong。
但最後幾個尤拉函式在p很大時貌似都等於$2^{i}$,所以直接在返回值等於0的時候返回$\phi(i)$也能過。
總複雜度$O(n\log^{2}{p})$。
套路:
- 看起來沒法維護的線段樹問題$\rightarrow$考慮是否可以在有限次操作後變為常數。
- 拓展尤拉定理:$a^{b}\ mod\ p=\begin{cases}a^{b},b<\phi(p) \\ a^{b\ mod\ \phi(p)+\phi(p)},b\geq \phi(p) \\ \end{cases}$
- 預處理快速冪:利用BSGS思想分塊預處理。
程式碼:
#include<bits/stdc++.h> #define maxn 200005 #define maxm 10000 #define inf 0x7fffffff #define ll long long #define rint register ll #define debug(x) cerr<<#x<<": "<<x<<endl #define fgx cerr<<"--------------"<<endl #define dgx cerr<<"=============="<<endl using namespace std; ll mod,A[maxn][50],phi[maxn]; ll bs[50][maxn],gs[50][maxn]; ll pri[maxn],ish[maxn]; inline ll read(){ ll x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } struct Segment{ ll mn[maxn<<2],lz[maxn<<2],sum[maxn<<2]; inline void build(ll l,ll r,ll k){ if(l==r){mn[k]=1,sum[k]=A[l][1];return;} ll mid=l+r>>1; build(l,mid,k<<1),build(mid+1,r,k<<1|1); mn[k]=min(mn[k<<1],mn[k<<1|1]); sum[k]=(sum[k<<1]+sum[k<<1|1])%mod; } inline void pushdown(ll k){ if(lz[k]){ lz[k<<1]+=lz[k],lz[k<<1|1]+=lz[k]; mn[k<<1]+=lz[k],mn[k<<1|1]+=lz[k]; lz[k]=0; } } inline void force(ll l,ll r,ll k){ if(mn[k]>phi[0]){sum[k]=A[1][phi[0]]*(r-l+1)%mod;return;} if(l==r){sum[k]=A[l][mn[k]];return;} pushdown(k); ll mid=l+r>>1; force(l,mid,k<<1),force(mid+1,r,k<<1|1); sum[k]=(sum[k<<1]+sum[k<<1|1])%mod; } inline void upd(ll x,ll y,ll l,ll r,ll k){ if(x<=l && r<=y){ mn[k]++,lz[k]++; if(mn[k]<=phi[0]) force(l,r,k); else sum[k]=A[1][phi[0]]*(r-l+1)%mod; return; } pushdown(k); ll mid=l+r>>1; if(x<=mid) upd(x,y,l,mid,k<<1); if(y>mid) upd(x,y,mid+1,r,k<<1|1); mn[k]=min(mn[k<<1],mn[k<<1|1]); sum[k]=(sum[k<<1]+sum[k<<1|1])%mod; } inline ll qry(ll x,ll y,ll l,ll r,ll k){ if(x<=l && r<=y) return sum[k]; pushdown(k); ll mid=l+r>>1,res=0; if(x<=mid) res=(res+qry(x,y,l,mid,k<<1))%mod; if(y>mid) res=(res+qry(x,y,mid+1,r,k<<1|1))%mod; return res; } }tr; inline ll pw(ll a,ll b,ll mo){ll r=1;while(b)r=(b&1)?r*a%mo:r,a=a*a%mo,b>>=1;return r;} inline ll pwp(ll mo,ll b){return bs[mo][b%maxm]*gs[mo][b/maxm]%phi[mo];} inline void Init(ll x){ for(ll i=2;i<x;i++){ if(!ish[i]) pri[++pri[0]]=i; for(ll j=1;j<=pri[0];j++){ if(pri[j]*i>=x) break; ish[pri[j]*i]=1; if(i%pri[j]==0) break; } } } inline void init(ll p){ phi[++phi[0]]=p; if(p==1){phi[++phi[0]]=1;return;} ll x=p,res=p; for(ll i=1;i<=pri[0];i++) if(x%pri[i]==0){ res=(res-res/pri[i]); while(x%pri[i]==0) x/=pri[i]; } if(x>1) res=(res-res/x); init(res); } inline ll solve(ll a,ll t,ll end){ if(t==end) return a; ll ft=solve(a,t+1,end); if(ft>=phi[t+1]) ft=ft%phi[t+1]+phi[t+1]; ll res=pwp(t,ft); return (!res)?phi[t]:res; } int main(){ ll n=read(),m=read(); mod=read(); ll c=read(); Init(maxm),init(mod); for(ll t=1;t<=phi[0];t++){ for(ll i=0;i<maxm;i++) bs[t][i]=pw(c,i,phi[t]); for(ll i=0;i<maxm;i++) gs[t][i]=pw(c,i*maxm,phi[t]); } for(ll i=1;i<=n;i++){ ll x=read(); for(ll j=1;j<=phi[0];j++) A[i][j]=solve(x,1,j); } //fgx; tr.build(1,n,1); while(m--){ ll op=read(),l=read(),r=read(); if(op==0) tr.upd(l,r,1,n,1); else printf("%lld\n",tr.qry(l,r,1,n,1)); } return 0; }相逢是問候