1. 程式人生 > >【BZOJ3884】上帝與集合的正確用法

【BZOJ3884】上帝與集合的正確用法

pan 相對 spa printf 可能 mat 一次 返回 space

Description

  
  一句話題意,給定\(p\)作為模數:
  
技術分享圖片
  
  \(p\le 10^7\),數據組數\(T\le1000\)
  
  
  

Solution

  
  看到就棄療了,再見......
  
  將模數\(p\)拆分成\(p=q2^k\),其中\(q\)為一個奇數。那麽:
  
  
\[ \begin{aligned} 2^{2^{2...}}mod\; p&=2^k(2^{2^{2..}-k}mod\;q)\&=2^k(2^{(2^{2..}-k)mod\;\varphi(q)}mod\;q) \end{aligned} \]
  考慮遞歸計算\((2^{2...}-k)\)

\(2^{2...}\),只不過模數由\(p\)變成\(\varphi(q)\)。當模數\(p\)變成1的時候,我們就遇到了邊界——不管裏面式子如何,模1都是0,直接返回0即可。考慮遞歸的層數:除了第一次調用的\(p\)可能是奇數之外,往下遞歸的\(p\)幾乎都是偶數(\(\varphi(x),x\ge3\)都是偶數),\(\varphi(q)\)相對於\(p\)大概會減少一倍。直到\(p=1\)時,層數不會太多,dalao說是\(O(log^2p\))。
  
  所以就直接遞歸計算了。實現上,如果先用線性篩篩出所有的\(\varphi\),太慢。每次調用\(\varphi\)時直接\(O(\sqrt n)\)
計算反而更加快。這兩種方法,是穩定300ms和6ms的差距......
  
  
  

Code

  

#include <cstdio>
using namespace std;
const int S=10000001;
inline int ksm(int x,int y,int MOD){
    int res=1;
    for(;y;x=1LL*x*x%MOD,y>>=1)
        if(y&1) res=1LL*res*x%MOD;
    return res;
}
int getPhi(int x){
    int res=x;
    for
(int i=2;i*i<=x;i++){ if(!(x%i)) res-=res/i; while(!(x%i)) x/=i; } if(x!=1) res-=res/x; return res; } int calc(int p){ if(p==1) return 0; int k=0,q=p; while(!(q&1)) k++,q>>=1; int phiq=getPhi(q); int mi=(calc(phiq)-k)%phiq; if(mi<0) mi+=phiq; return 1LL*ksm(2,mi,q)*ksm(2,k,p)%p; } int main(){ int T,p; scanf("%d",&T); while(T--){ scanf("%d",&p); printf("%d\n",calc(p)); } return 0; }

【BZOJ3884】上帝與集合的正確用法