题目传送门:【BZOJ 3884】
题目大意:
多组测试数据。输入整数 p,求
其中1 ≤ p ≤ 107 ,测试数据组数 ≤ 1000。
题目分析:
(一开始看到有无穷多个 2 就懵逼了)
其实这道题没有那么难,因为,你可以试一些数据,比如 p = 2 , 3 , 4 , 5 , 6 , …… 然后发现,随着 2 的幂次方的增长,模下来的数总是一个定值;这之后,我们就要使用数学归纳法(找规律)来得到正确的答案。
欧拉定理:若 gcd ( a , b ) = 1,则 a φ(b)≡1(modb) 。
那么对于这道题,它该怎么用呢?显然,对于 22222... ,我们要对它化简,于是我们可以考虑使用这个定理把它的幂转化成较小的数(根据同余的性质)。
举个栗子,gcd ( 4 , 5 ) = 1,则 4φ(5)≡44≡1(mod5) ;如果我们要求解 499mod5 的值, 那么,这个式子可以转化为: (44)24∗43mod5 ;前半部分由于同余于 1 直接被消掉,实质上它就变成了求 43mod5 的值了,也可以表示为 499modφ(5)mod5 。这样一来就要简单许多。
同样对于这道题而言,2 的无穷多次方也能转化成这种形式。因为
22222...
只含 2 这个质因子,所以我们可以先把 p 中的 2 提出来,变成
2k∗(22222...−k)modp2k
;
因为此时 gcd ( 2 ,
p2k
) = 1,所以可以套用欧拉定理,变成
2k∗22222...−kmodφ(p2k)modp2k
。
之后,对于 2222...−kmodφ(p2k) ,我们可以递归地对它进行处理,直到 p2k = 1 为止(此时模数为 1,意味着任何数模 1 都为 0,于是返回)。然后再加上每次递归时还留下的 kmodφ(p2k) 这个数,回溯回去求出每次取模之后的值,统计答案即可。
本题还有一些需要注意的地方:记得在快速幂时取模!还要开 long long!
下面附上代码:
- #include<iostream>
- using namespace std;
- typedef long long LL;
- const int MX=100005;
- int T;
- LL pp;
- LL getphi(LL x){ //据说根号 n 的求法更快?
- LL r=x,tmp=x;
- for (int i=2;i*i<=tmp;i++){
- if (tmp%i==0){
- r=r/i*(i-1);
- while (tmp%i==0) tmp/=i;
- }
- }
- if (tmp>1) r=r/tmp*(tmp-1);
- return r;
- }
- LL fastpow(LL a,LL b,LL mod){
- LL r=1,base=a;
- while (b){
- if (b&1) r=r*base%mod;
- base=base*base%mod;
- b>>=1;
- }
- return r%mod;
- }
- LL solve(LL p,LL k){ //k:每次取模之后的偏移量(+/-)
- if (p==1)
- return 0;
- LL f2=1,addtime=0; //f2:因子2的数量 addtime:2的幂次方
- while (p%2==0)
- f2<<=1,p/=2,addtime++; //求出2的因子数
- LL phi_p=getphi(p);
- LL temp1=solve(phi_p , ((-addtime%phi_p)+phi_p)%phi_p);
- LL temp2=fastpow(2,temp1,f2*p)%(f2*p)*f2 + k%(f2*p);
- //两个临时变量
- return temp2%(f2*p);
- }
- int main(){
- cin>>T;
- while (T–){
- cin>>pp;
- cout<<solve(pp,0)<<endl;
- }
- return 0;
- }