【BZOJ3884】上帝与集合的正确用法

Description

  
  一句话题意,给定\(p\)作为模数:
  
img
  
  \(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;
}

猜你喜欢

转载自www.cnblogs.com/RogerDTZ/p/9210601.html