模板汇总3.2_数学2

1.(EX)BSGS

①什么是BSGS

用来在$O(sqrt(n))$的时间内求关于$x$的方程 $a^x \equiv b (mod$ $p)$的解

②BSGS的原理

这里分块思想又出现了!

以$m=sqrt(p)$(注意向上取整)为标准分块,打出一张$a^k(k∈[0,m)$ $)$的表,然后开始捣鼓原式

把左边变成$a^{i*m-j}$这样的形式,然后移项之后就变成了$a^{i*m}\equiv a^j+b(mod$ $p)$这样子,于是就可以从枚举$i$然后查表验证了,这样不计算查表的复杂度的话就是$O(sqrt(p))$的复杂度了

然后发现这个只能解决$a,p$是互质的情况,不互质怎么办?

设$g=gcd(a,p)$,然后将原式

$a^x \equiv b(mod$ $p)$

变形为(注意此时若$b$不被$g$整除则无解)

$a^{x-1}*\frac{a}{g} \equiv \frac{b}{g}(mod$ $\frac{p}{g})$

不断按照这种方式规约,直到$gcd(a,p')=1$,这样规约复杂度为$sqrt(p)$,不影响整体的复杂度

③BSGS的复杂度

时间复杂度$O(sqrt(n))$(如果不算表的复杂度的话,因为可以哈希表/unordered_map(C++11) )

④BSGS的具体实现

(不知道为什么SPOJ过不了(WA),然后懒得写哈希了就用map+O2滚过去了)

 1 // luogu-judger-enable-o2
 2 #include<map>
 3 #include<cmath>
 4 #include<cstdio>
 5 #include<cstring>
 6 #include<algorithm>
 7 using namespace std;
 8 const int inf=2147483647;
 9 int T,x,y,t1,t2,t3;
10 map<long long,int> mmp;
11 int gcd(int a,int b)
12 {
13     return b?gcd(b,a%b):a; 
14 }
15 int EXGCD(int a,int b,int &x,int &y)
16 {
17     if(!b) {x=1,y=0; return a;}
18     int tmp=EXGCD(b,a%b,x,y),tep=x;
19     x=y,y=tep-a/b*x; return tmp;
20 }
21 int Inv(int a,int b)
22 {
23     EXGCD(a,b,x,y);
24     return (x%b+b)%b;
25 }
26 long long qpow(long long x,long long k,long long m)
27 {
28     if(!k) return 1%m;
29     if(k==1) return x%m;
30     long long tmp=qpow(x,k/2,m);
31     return k%2?tmp*tmp%m*x%m:tmp*tmp%m;
32 }
33 int BSGS(int bas,long long res,int mod)
34 {
35     long long tmp,tep,sqr=ceil(sqrt(mod)); 
36     mmp.clear(),mmp[tmp=res%mod]=0;
37     for(int i=1;i<=sqr;i++)
38         tmp=tmp*bas%mod,mmp[tmp]=i;
39     tmp=1,tep=qpow(bas,sqr,mod);
40     for(int i=1;i<=sqr;i++)
41     {
42         tmp=tmp*tep%mod;
43         if(mmp.count(tmp))
44             return sqr*i-mmp[tmp];
45     }
46     return -inf;
47 }
48 int EXBSGS(int bas,long long res,int mod)
49 {
50     int gnm,knm=1,ret=0;
51     while((gnm=gcd(bas,mod))!=1)
52     {
53         if(res%gnm) return -inf;
54         ret++,res/=gnm,mod/=gnm;
55         knm=1ll*bas/gnm*knm%mod;
56     }
57     res*=Inv(knm,mod);
58     if(1%mod==res) return ret;
59     if(bas%mod==0) return res?-inf:ret+1;
60     return BSGS(bas,res,mod)+ret;
61 }
62 int main()
63 {
64     while(true)
65     {
66         scanf("%d%d%d",&t1,&t2,&t3);
67         if(t1||t2||t3) 
68         {
69             int ans=EXBSGS(t1,t3,t2);
70             if(ans<0) puts("No Solution");
71             else printf("%d\n",ans);
72         }
73         else break;
74     }
75     return 0;
76 }
View Code

2.Miller-Rabin素性测试

①什么是素性测试

就是判素数,大家都会的$O(sqrt(n))$的那个

②Miller-Rabin素性测试的原理

首先我们需要了解一个东西

费马小定理:若正整数$a$小于$p$且$p$是素数,则有$a^(p-1)$ $mod$ $p=1$,不证。

那么它的逆定理就应该长这样:若所有小于$p$的正整数$a$满足$a^(p-1)$ $mod$ $p=1$,则$p$是素数— —看起来这个逆定理用来判素数很有用,我们只需要枚举所有小于$p$的素数判就可以了,而一般判不了几个素数就能发现$p$不是素数了。或者为了提高效率可以牺牲一下复杂度— —只用几个素数来判断,然后把那些判不出来的打个表......这个东西大概叫做“Fermat素性测试”

遗憾的是它的逆定理是假的,不过好消息是不满足它的逆定理的数一定不是素数,只是满足的也不一定是素数,人们把这些数叫做“伪素数”(于是我们知道打表也是行不通的,在$10^{10}$范围内就有$600$个这样的数— —所有小于它的素数都无法探测出它是一个合数,其中最小的是$561$),于是有了Miller-Rabin素性测试。

我们又需要了解一个东西:

二次探测定理:对于一个奇素数$p$,在小于它的所有正整数$b$中,只有$1$和$p-1$满足$b^2 \equiv 1(mod$ $p)$,不证

当一个数通过以某数为底的“Fermat素性测试时”,再使用二次探测定理探测一次(下面说怎么做),当然这样还是可能有一些“强伪素数”通过了探测,不过能通过以$2$和$3$为底的第一个强伪素数已经达到了$1373653$,.所以多搞几个素数或者找个素数表从里面多rand几次基本不会出错,事实上:

Miller-Rabin素性探测失败的概率为$\frac{1}{4^k}$,其中$k$是选取的底数的个数

然而我们还没说如何用二次探测定理探测,具体来说是这样的:

1.将$p1$提取出表示为$x*2^k$的形式

2.选取一个底数$b$并求出$b'=b^{x}(mod$ $p)$

3.将$b'$再平方$k$次,如果结果中未出现$p-1$,则这个数未通过二次探测

③Miller-Rabin素性测试的复杂度

时间复杂度$O(klog$ $p)$

④Miller-Rabin素性测试的具体实现

 中间压得有点丑=。=

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int pri[8]={0,2,3,7,23,59,101,233};
 6 long long n,T,rd; 
 7 long long bmul(long long a,long long b,long long m) 
 8 {
 9     long long tmp=((long double)a/m*b+1e-7);
10     long long ret=a*b-tmp*m;
11     return ret<0?ret+m:ret;
12 }
13 long long qpow(long long x,long long k,long long p)
14 {
15     if(k==1) return x%p;
16     long long tmp=qpow(x,k/2,p);
17     return k%2?bmul(bmul(tmp,tmp,p),x,p):bmul(tmp,tmp,p);
18 }
19 bool Miller_Rabin(long long x)
20 {
21     for(int i=1;i<=7;i++)
22         if(x==pri[i]) return true;
23     if(x%2==0||x==1) return false;
24     long long nm=x-1,pw=0;
25     while(nm%2==0) nm/=2,pw++;
26     for(int i=1;i<=7;i++)
27     {
28         long long fm=qpow(pri[i],nm,x);
29         if(fm==1||fm==x-1) continue;
30         bool mr=false; 
31         for(int j=1;j<=pw;j++)
32         {
33             fm=bmul(fm,fm,x);
34             if(fm==x-1) {mr=true; break;}
35         }
36         if(!mr) return false;
37     }
38     return true;
39 }
40 int main()
41 {
42     scanf("%lld%lld",&n,&T);
43     while(T--)
44         scanf("%lld",&rd),Miller_Rabin(rd)?puts("Yes"):puts("No");
45     return 0;
46 } 
View Code

3.Pollard-Rho因数分解

①Pollard-Rho是干什么的

在期望复杂度$O(x^{\frac{1}{4}})$的复杂度下找到$x$的一个因数

②Pollard-Rho的具体实现

首先Miller-Rabin判素,不谈

然后考虑我们最朴素的分解方法:我们从$1$到$sqrt(n)$枚举然后试除

然后发现这样很慢,考虑优化这个过程,我们随机一个数$x$,然后求$gcd(x,n)$,如果大于$1$就递归分解。

不是咋办?再rand一个显然很睿智,我们用一种类似于在模$x$剩余系下“行走”的方法,用“距离”表示这个用来尝试的数:

找两个数$stp1$和$stp2$,每次$stp1$平方后加上一个$[1,x-1]$范围内的“步长”,然后用$abs(stp1-stp2)$乘上当前的“距离”,这样“走”2的整次幂步后看看距离是否和$x$有公因数,没有就用$stp1$代替$stp2$,然后“步数”加倍再继续走。首先这样一定是能找到$x$的因数的,然后根据生日悖论这样的期望复杂度是$O(x^{\frac{1}{4}})$,显然我不会证明=。=

③Pollard-Rho的复杂度

上面说了。。。

④Pollard-Rho的具体实现

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int pri[8]={0,2,3,7,23,59,101,233};
 6 long long T,rd,maxf; 
 7 long long bmul(long long a,long long b,long long m) 
 8 {
 9     long long tmp=((long double)a/m*b+1e-7);
10     long long ret=a*b-tmp*m;
11     return ret<0?ret+m:ret;
12 }
13 long long qpow(long long x,long long k,long long p)
14 {
15     if(k==1) return x%p;
16     long long tmp=qpow(x,k/2,p);
17     return k%2?bmul(bmul(tmp,tmp,p),x,p):bmul(tmp,tmp,p);
18 }
19 long long gcd(long long a,long long b) 
20 {
21     return b?gcd(b,a%b):a;
22 }
23 bool Miller_Rabin(long long x)
24 {
25     for(int i=1;i<=7;i++)
26         if(x==pri[i]) return true;
27     if(x%2==0||x==1) return false;
28     long long nm=x-1,pw=0;
29     while(nm%2==0) nm/=2,pw++;
30     for(int i=1;i<=7;i++)
31     {
32         long long fm=qpow(pri[i],nm,x);
33         if(fm==1||fm==x-1) continue;
34         bool mr=false; 
35         for(int j=1;j<=pw;j++)
36         {
37             fm=bmul(fm,fm,x);
38             if(fm==x-1) {mr=true; break;}
39         }
40         if(!mr) return false;
41     }
42     return true;
43 }
44 long long Pollard_Rho(long long x) 
45 {
46     long long stp1=0,stp2=0,ret=1;
47     long long dist=1,cnt=2,walk=rand()%(x-1)+1;
48     while(true) 
49     {
50         for(int i=1;i<=cnt;i++) 
51         {
52             stp1=bmul(stp1,stp1,x)+walk;
53             dist=bmul(dist,abs(stp1-stp2),x);
54         }
55         if(ret>1||(ret=gcd(dist,x))>1) break;
56         cnt*=2,stp2=stp1,dist=1;
57     }
58     if(ret==x)
59     {
60         ret=1;
61         while(ret==1)
62         {
63             stp1=bmul(stp1,stp1,x)+walk;
64             ret=gcd(abs(stp1-stp2),x);
65         }
66     }
67     return ret;
68 }
69 void Decompose(long long x)
70 {
71     if(x==1||x<=maxf) return;
72     if(Miller_Rabin(x))
73     {
74         maxf=max(x,maxf);
75         return ;
76     }
77     long long p=x;
78     while(p==x) p=Pollard_Rho(x);
79     Decompose(p),Decompose(x/p);
80 }
81 int main()
82 {
83     scanf("%lld",&T);
84     while(T--)
85     {
86         scanf("%lld",&rd);
87            maxf=0,Decompose(rd);
88         if(maxf==rd) puts("Prime");
89         else printf("%lld\n",maxf);
90     }
91     return 0;
92 } 
View Code

4.杜教筛

①这是啥

一种亚线性筛,可以$O(n^{\frac{2}{3}})$筛出一些特定的数论函数的前缀和

②实现原理

构造一个函数和要求的数论函数卷起来,对卷积求前缀和

$\sum\limits_{i=1}^n(f×g)(i)$

$=\sum\limits_{i=1}^n\sum_{d|i}f(d)g(\frac{i}{d})$

$=\sum\limits_{i=1}^ng(i)\sum\limits_{j=1}^{\left\lfloor\frac{n}{i}\right\rfloor}f(j)$

下面为了方便设$f$的前缀和为$S$

$=\sum\limits_{i=1}^ng(i)S(\left\lfloor\frac{n}{i}\right\rfloor)$

那怎么搞出来我们要求的前缀和呢?直接作差

$ans=\sum\limits_{i=1}^ng(i)S(\left\lfloor\frac{n}{i}\right\rfloor)-\sum\limits_{i=2}^ng(i)S(\left\lfloor\frac{n}{i}\right\rfloor)$

前面那一项换回刚才的$\sum\limits_{i=1}^n(f×g)(i)$,那么我们只需要快速算$g$和$f×g$的前缀和,然后递归下去就可以了。根据(我不会的)积分知识,当我们预处理出前$O(n^{\frac{2}{3}})$的$f$时复杂度最好,为$O(n^{\frac{2}{3}})$

顺便说一句常见的两个函数φ和μ的配法:都是卷常函数,卷出来分别是单位函数和狄利克雷单位函数,很好求

③时间复杂度

$O(n^{\frac{2}{3}})$

④具体实现

 1 #include<map>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int N=5e6+50,Edge=3e6,mod=1e9+7;
 7 int T,n,cnt,pri[N],npr[N],mul[N];
 8 long long phi[N]; map<int,long long> mp1,mp2;
 9 void Prework()
10 {
11     mul[1]=phi[1]=1,npr[1]=true;
12     for(int i=2;i<=Edge;i++)
13     {
14         if(!npr[i]) pri[++cnt]=i,mul[i]=-1,phi[i]=i-1;
15         for(int j=1;j<=cnt&&1ll*i*pri[j]<=Edge;j++)
16         {
17             npr[i*pri[j]]=true;
18             phi[i*pri[j]]=phi[i]*pri[j];
19             if(i%pri[j]) 
20                 mul[i*pri[j]]=-mul[i],
21                 phi[i*pri[j]]-=phi[i];
22             else 
23                 break;
24         }
25         phi[i]+=phi[i-1],mul[i]+=mul[i-1];
26     }
27 }
28 long long Dusieve_phi(int x)
29 {
30     if(x<=Edge) return phi[x];
31     if(mp1.count(x)) return mp1[x];
32     long long ret=1ll*(x+1)*x/2;
33     for(int i=2,j;i<=x;i=j+1)
34         j=x/(x/i),ret-=1ll*Dusieve_phi(x/i)*(j-i+1);
35     return mp1[x]=ret;
36 }
37 int Dusieve_mul(int x)
38 {
39     if(x<=Edge) return mul[x];
40     if(mp2.count(x)) return mp2[x];
41     int ret=1;
42     for(int i=2,j;i<=x;i=j+1)
43         j=x/(x/i),ret-=Dusieve_mul(x/i)*(j-i+1);
44     return mp2[x]=ret;
45 }
46 int main()
47 {
48     Prework(),scanf("%d",&T);
49     while(T--)
50     {
51         scanf("%d",&n);
52         long long ans1=Dusieve_phi(n);
53         int ans2=Dusieve_mul(n);
54         printf("%lld %d\n",ans1,ans2);
55     }
56     return 0;
57 }
View Code

 

posted @ 2018-11-27 20:46  Speranza_Leaf  阅读(236)  评论(0)    收藏  举报