1. 程式人生 > >LOJ 2142 相逢是問候(擴充套件尤拉定理+線段樹)

LOJ 2142 相逢是問候(擴充套件尤拉定理+線段樹)

題意

https://loj.ac/problem/2142

思路

一個數如果要作為指數,那麼它不能直接對模數取模,這是常識;

諸如 \(c^{c^{c^{c..}}}\) 的函式遞增飛快,不是高精度可以描述的,這也是常識。

所以,此題要用到很多數論知識。

尤拉函式

定義

\(\varphi(n)\)\([1,n]\) 中與 \(n\) 互質的正整數個數(包括 \(1\))。

通式

\(\displaystyle \varphi(n)=n\prod_{p|n}(1-{1\over p})\) 其中 \(p\)\(x\) 的質因子。

如何理解這個式子呢?可以粗略這樣理解:\(\displaystyle (1-{1\over p})\)

意思就是篩掉所有能被 \(p\) 整除的數,當然這種理解方法是錯誤的,只能算是感性理解,方便記憶。

前幾項

摘自維基百科:
尤拉函式前143項

性質

幾條比較重要的性質:
\(\gcd(m,n)=1\),有 \(\varphi(mn)=\varphi(m)\varphi(n)\)
\(m=2\),不難得出當 \(n\) 為奇數時,\(\varphi(2n)=\varphi(n)\)
另外,對於質數 \(p\),有 \(\varphi(p)=p-1\)

尤拉定理

\(a^{\varphi(n)} \equiv 1 \pmod n\) 其中 \(\gcd(a,n)=1\)

\(n\) 為質數的情況就是著名的費馬小定理

擴充套件尤拉定理

\[ a^b=\begin{cases} a^b\quad \quad \quad \quad\quad \quad \ \ b<\varphi(p)\\ a^{b\mod\varphi(p)+\varphi(p)}\quad b\geq \varphi(p) \end{cases}\pmod p \]

擴充套件尤拉定理把冪的層數和模數都降了下來,用它就可以解決本題了。

迴歸本題

先考慮暴力做法再嘗試優化。我們可以給每個數一個標記,表示多少層 \(c\) 上才是 \(a_i\)。然後每次只需計算這個 \(c^{c^{c^{...^{a_i}}}}\) 即可。

套用擴充套件尤拉定理,假設標記為 \(3\),就是計算\(c^{c^{c^{a_i}}} \mod p\) 的值。

分兩類討論,當 \(c^{c^{a_i}}<\varphi(p)\),問題就轉化成了求 \(c^{c^{a_i}}\mod \varphi(p)\) 的值;否則,只需在此基礎上加 \(\varphi(p)\) 即可,而判斷大小隻需在快速冪上傳一個標記就行了。
一路遞迴下去,當模數變成 \(1\) 時直接返回 \(0\) 即可。

考慮優化。由於模數給定,那麼變化顯然是從 \(p\)\(\varphi(p)\) 再到 \(\varphi(\varphi(p))\) 這樣一路變化的。變化不會超過 \(\log p\) 層,那麼總標記次數就不超過 \(n\log p\) 次。

可以用線段樹維護一個區間,那麼再修改一個區間時,也維護一個區間的最小修改次數的最大值,當它對應的模數已經是 \(1\) 時,便不進行修改。再修改時,要算出 \(c^{c^{c^{...^{a_i}}}}\) 的值,最多修改 \(n\log n\) 次,最深又有 \(\log\) 層,算快速冪又有一個 \(\log\)。三個 \(\log\) 是過不了本題的。

然而快速冪可以直接分塊預處理,由於模數不超過 \(\log n\) 個,則分別預處理
\(c^{i} \mod p_k,c^{50000i} \mod p_k\) 的值,以及和 \(p_k\) 的大小關係即可。複雜度降至兩個 \(\log\)

查詢直接區間求和,一個 \(\log\)

程式碼

#include<bits/stdc++.h>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
typedef long long LL;
using namespace std;
const int N=5e4+5;
const int _N=5e4;
int P[105],a[N],tot,n,m,c;
LL PW[2][N][105];bool FL[2][N][105];
//PW[i][j][k]  c^(i*_N+j)%P[k]
//FL[i][j][k] [c^(i*_N+j)>=P[k]]
LL phi(LL n)
{
    LL res=n;
    for(LL i=2;i*i<=n;i++)if(n%i==0)
    {
        res=res/i*(i-1);
        while(n%i==0)n/=i;
    }
    if(n>1)res=res/n*(n-1);
    return res;
}
struct node
{
    int Mi,sum;
    node operator +(const node &_)const
    {
        return (node){min(Mi,_.Mi),(sum+_.sum)%P[0]};
    }
}nd[N<<2];
void build(int k,int l,int r,int *arr)
{
    if(l==r)
    {
        nd[k].Mi=0;
        nd[k].sum=arr[l];
        return;
    }
    int mid=(l+r)>>1;
    build(k<<1,l,mid,arr);
    build(k<<1|1,mid+1,r,arr);
    nd[k]=nd[k<<1]+nd[k<<1|1];
}
LL Pow(LL p,int id,bool &flag)//c^p%P[id] flag=[c^p>=P[id]]
{
    flag=FL[0][p%_N][id]||FL[1][p/_N][id]||(PW[0][p%_N][id]*PW[1][p/_N][id]>=P[id]);
    return PW[0][p%_N][id]*PW[1][p/_N][id]%P[id];
}
LL calc(int x,LL y)
{
    if(y>=P[x])y=y%P[x]+P[x];
    DOR(i,x,1)
    {
        bool flag;
        y=Pow(y,i-1,flag);
        if(flag)y+=P[i-1];
    }
    return y%P[0];
}
void update(int k,int L,int R,int l,int r)
{
    if(L<=l&&r<=R&&nd[k].Mi>=tot)return;
    if(l==r)
    {
        nd[k].sum=calc(++nd[k].Mi,a[l]);
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)update(k<<1,L,R,l,mid);
    if(R>mid)update(k<<1|1,L,R,mid+1,r);
    nd[k]=nd[k<<1]+nd[k<<1|1];
}
int query(int k,int L,int R,int l,int r)
{
    if(L<=l&&r<=R)return nd[k].sum;
    int mid=(l+r)>>1;
    if(R<=mid)return query(k<<1,L,R,l,mid);
    else if(L>mid)return query(k<<1|1,L,R,mid+1,r);
    else return (query(k<<1,L,R,l,mid)+query(k<<1|1,L,R,mid+1,r))%P[0];
}
void pre_compute()
{
    FOR(i,0,tot)
    {
        PW[0][0][i]=1,FL[0][0][i]=(1>=P[i]);
        FOR(j,1,_N)
        {
            PW[0][j][i]=PW[0][j-1][i]*c;
            FL[0][j][i]=FL[0][j-1][i]|(PW[0][j][i]>=P[i]);
            if(PW[0][j][i]>=P[i])PW[0][j][i]%=P[i];
        }
        
        PW[1][0][i]=1,FL[1][0][i]=(1>=P[i]);
        FOR(j,1,_N)
        {
            PW[1][j][i]=PW[1][j-1][i]*PW[0][_N][i];
            FL[1][j][i]=FL[1][j-1][i]|(PW[1][j][i]>=P[i]);
            if(PW[1][j][i]>=P[i])PW[1][j][i]%=P[i];
        }
    }
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&P[tot=0],&c);
    FOR(i,1,n)scanf("%d",&a[i]);
    while(P[tot]!=1)
    {
        P[tot+1]=phi(P[tot]);
        tot++;
    }
    P[++tot]=1;
    pre_compute();
    build(1,1,n,a);
    while(m--)
    {
        int op,l,r;
        scanf("%d%d%d",&op,&l,&r);
        if(op==0)update(1,l,r,1,n);
        else printf("%d\n",query(1,l,r,1,n));
    }
    return 0;
}