【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;
}
posted @ 2018-06-21 19:09  RogerDTZ  阅读(128)  评论(0编辑  收藏  举报