1. 程式人生 > >BZOJ4869:[SHOI2017]相逢是問候——題解

BZOJ4869:[SHOI2017]相逢是問候——題解

要求 href 吐槽 個數 uil 我的博客 prim size ctype

http://www.lydsy.com/JudgeOnline/problem.php?id=4869

題面復制於洛谷:https://www.luogu.org/problemnew/show/P3747#sub

技術分享圖片

參考洛谷的前兩篇(也是僅有的兩篇)題解。

首先我們要知道一個公式:

技術分享圖片

這又被叫做擴展歐拉定理,證明我們並不關心。

有了擴展歐拉定理,我們就能夠避免高精度從而求出對於任意一個數的0操作之後變成什麽數了。

(遞歸或者叠代選一個,遞歸好理解,叠代有助於理解下面的題解,而且常數小)

我們又有一個結論,對於一個p,它無限遞歸p=phi(p)直到p=1為止的深度為O(logp)。

這樣的好處在於我們雖然修改了很多次,但是當修改次數大於logp的時候,此時你再怎麽修改也沒有用了因為你的指數為1相當於沒有操作。

那麽顯然對於1我們記錄該元素被操作了幾次,然後暴力修改即可,可用線段樹維護。復雜度O(nlognlogp)。(請註意這個復雜度是假的)

這樣的復雜度我們交到bzoj上是沒有問題的,但是交到洛谷上會TLE3個點。將遞歸改成叠代,預處理每個p的phi,各種常數優化也會TLE2個點。

emmm……why?

當然是因為我們的復雜度沒算對啊。

對於單點修改,顯然每次修改是O(logplogp)……等等,怎麽多出來一個O(logp)。

忘了我們使用了快速冪了嗎,我們多出來的O(logp)就是這麽來的。

考慮除掉這個O(logp),顯然預處理快速冪。

如果你寫的是叠代的話,你就會發現底數永遠都是c不變,變的只是指數和模數, 且指數最大是p=1e8。

我們可以先求出不同模數且指數<=1e5的c的冪,我們還可以求不同模數且指數=整1e5的c的冪。

這就很像分塊了,顯然當我們要求指數為k時,k=x*1e5+y(y<1e5)顯然可求。

這樣我們預處理出所有的數在多少次操作後的值,則我們的復雜度就是O(nlognlogp)。

吐槽:最開始學完擴歐之後覺得這題洛谷給的難度高了,怎麽就NOI+了,後來在TLE之後一看woc還有這種操作……

神題神題……

(然而博主並不想寫正解,放的代碼只能過bzoj,正解如果有時間的話會補上的emmm)

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<map>
using namespace std;
typedef long long ll;
const int N=5e4+5;
const int O=1e4+5;
inline int read(){
    int X=0,w=0;char ch=0;
    while(!isdigit(ch)){w|=ch==-;ch=getchar();}
    while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    return w?-X:X;
}
struct tree{
    ll v,t;
}tr[N*4];
int su[O],he[O],cnt,phi[40],n,m;
ll p,c,logp,b[N];
bool ok;
inline ll qpow(ll k,int p){
    ll ans=1,s=c;
    while(k){
        if(k&1)ans=ans*s;
        s*=s;k>>=1;
        if(s>=p)ok=1,s%=p;
        if(ans>=p)ok=1,ans%=p;
    }
    return ans;
}
int Euler(int k){
    int res=k;
    for(int i=1;su[i]*su[i]<=k;i++){
        if(k%su[i]==0){
            res-=res/su[i];
            while(k%su[i]==0)k/=su[i];
        }
    }
    if(k>1)res-=res/k;
    return res;
}
void prime(){
    for(int i=2;i<O;i++){
        if(he[i]==0){
            cnt++;
            su[cnt]=i;
        }
        for(int j=1;j<=cnt&&i*su[j]<O;j++){
            he[su[j]*i]=1;
            if(i%su[j]==0)break;
        }
    }
    phi[logp]=p;
    while(phi[logp]!=1)phi[++logp]=Euler(phi[logp-1]);
    phi[++logp]=1;
}
void build(int a,int l,int r){
    if(l==r){
        tr[a].v=b[l]%p;
        return;
    }
    int mid=(l+r)>>1;
    build(a<<1,l,mid);build(a<<1|1,mid+1,r);
    tr[a].v=(tr[a<<1].v+tr[a<<1|1].v)%p;  
}
ll suan(ll v,ll k){
    ll tmp=v;
    if(tmp>phi[k])tmp=tmp%phi[k]+phi[k];
    for(int i=k;i>0;i--){
        ok=0;tmp=qpow(tmp,phi[i-1]);
        if(ok)tmp+=phi[i-1];
    }
    return tmp;
}
void gai(int a,int l,int r,int l1,int r1){
    if(tr[a].t>=logp)return;
    if(r<l1||r1<l)return;
    if(l==r){
        tr[a].t++;
        tr[a].v=suan(b[l],tr[a].t);
        return;
    }
    int mid=(l+r)>>1;
    gai(a<<1,l,mid,l1,r1);gai(a<<1|1,mid+1,r,l1,r1);
    tr[a].v=(tr[a<<1].v+tr[a<<1|1].v)%p;
    tr[a].t=min(tr[a<<1].t,tr[a<<1|1].t);
}
ll wen(int a,int l,int r,int l1,int r1){
    if(r<l1||r1<l)return 0;
    if(l1<=l&&r<=r1)return tr[a].v;
    int mid=(l+r)>>1;
    return (wen(a<<1,l,mid,l1,r1)+wen(a<<1|1,mid+1,r,l1,r1))%p;
}
int main(){
    n=read(),m=read(),p=read(),c=read();
    prime();
    for(int i=1;i<=n;i++)b[i]=read();
    build(1,1,n);
    for(int i=1;i<=m;i++){
        int op=read(),l=read(),r=read();
        if(!op)gai(1,1,n,l,r);
        else printf("%lld\n",wen(1,1,n,l,r));
    }
    return 0;
}

+++++++++++++++++++++++++++++++++++++++++++

+本文作者:luyouqi233。               +

+歡迎訪問我的博客:http://www.cnblogs.com/luyouqi233/+

+++++++++++++++++++++++++++++++++++++++++++

BZOJ4869:[SHOI2017]相逢是問候——題解