数论进阶——kuangbin模板+计蒜客课程指引

  • 导言:数论以及基础数学在程序设计竞赛中充当了相当的角色。很多题看似模拟与构造,最终都可归类成数学题目。因此掌握数论的逻辑思维,对于看待程序设计竞赛的题目会有更多简洁而优美的实现技巧。
  • 二次修改后的导言:经过不算很缜密的思考后,还是将快速(矩阵)幂和欧拉函数的章节提前了。如果我有幸让读者读到这篇博客,请读者注意一下:欧拉函数与关于质数的章节有部分知识是交错的,大家参考的时候可以交错的看
笔者知识有限,如有纰漏,敬请指出,谢谢!

数论和基础数学

  • 快速幂qp和矩阵快速幂s_qp
    • 大指数取模
    • 矩阵快速幂
  • 整除和取余Exact division&Remainder
    • 整除
    • 取余
    • 关于gcd和lcm
  • 欧拉函数φ(x)与积性函数Euler&Multiplicative
    • 欧拉函数φ(x)
    • 积性函数
    • 欧拉定理
    • 费马小定理
    • 其他性质
    • 原根primitive root
      • 求原根
    • 线性筛(同时得到欧拉函数以及素数表)
  • 关于质数Prime
    • 质数检测法
    • 埃式筛法
    • 欧式筛法
    • 贴一道综合题
    • 将一个合数按最小质数依次分解,并统计个数
    • Fermat素数测试
    • Miller_Rabin素数测试算法
      • 二次探测定理优化
        • 技巧
  • 扩展欧几里得Extra Euclid
    • 乘法逆元
      • 线性预处理逆元inverse
      • 用费马小定理来求逆元(模数是质数)
      • 用欧拉定理求逆元(模不一定是质数)
      • 无敌初始化(求出阶乘模,乘法逆元,阶乘逆元)
    • 中国剩余定理(模线性方程组)
      • 样式
      • 解法:
  • 高斯消元法Gauss elimination
    • 高斯消元处理同余方程组
  • 傅里叶变换&&数论变换Fourier transform
    • 离散傅里叶变换(DFT)&&快速傅里叶变换(FFT)
    • 快速数论变换(NTT)
  • 母函数(生成函数)Generating function
    • 五边形数和欧拉函数和分割函数的那些事
  • 求 A^B 的约数之和对 MOD 取模(唯一分解定理和)
  • 莫比乌斯反演
  • Baby-Step Giant-Step
  • 自适应Simpson积分(保证精度的积分算法)
  • 斐波那契数列取模循环节
  • 其它公式
    • Polya

快速幂qp和矩阵快速幂s_qp

快速幂已经很多人介绍过了,相信读者有了解过。也不难懂,直接上代码:

LL qp(LL base,LL n)
{
	LL res = 1;
	while(n){
		if(n&1) res = res * base;
		base = base * base;
		n>>=1;
	}
	return res;
}

大指数取模

比如 2 233 m o d 100 2^{233}mod100 2233mod100,不能算出 2 233 2^{233} 2233再去取模。利用同余定理可以写出: ( 2 ∗ 2 ∗ 2... ∗ 2 ) m o d 100 (2*2*2...*2)mod100 (222...2)mod100 = ( ( 2 m o d 100 ∗ 2 m o d 100 ) m o d 100... ) m o d 100 ((2mod100*2mod100)mod100...)mod100 ((2mod1002mod100)mod100...)mod100那么只需要在快速幂的基础上base与res取模即可。
代码:

LL qp(LL base,LL n,LL mod)
{
	LL res = 1;
	while(n){
		if(n&1) res = (res%mod * base%mod)%mod;
		base = (base%mod * base%mod)%mod;
		n>>=1;
	}
	return res;
}

矩阵快速幂

代码:

#include 
using namespace std;
int n;

struct matrix{
    int a[100][100];
};
matrix matrix_mul(matrix A,matrix B,int mod){
    matrix C;
    for(int i=0;i<n;++i){
		 for(int j=0;j<n;++j){
             C.a[i][j] = 0;
             for(int k=0;k<n;++k){
                 C.a[i][j] += A.a[i][k] * B.a[k][j] %mod;
                 C.a[i][j] %= mod;
             }
         }
    }
    return C;
}

matrix unit(){
    matrix res;
    for(int i=0;i<n;++i){
        for(int j=0;j<n;++j){
            if(i==j){ res.a[i][j] = 1;}
            else{
                res.a[i][j] = 0;
            }
        }
    }
    return  res;
}

matrix matrix_pow(matrix A,int y,int mod){
    matrix res = unit(),temp = A;
    for(;y;y/=2){
        if(y&1){
            res = matrix_mul(res,temp,mod);
        }
        temp = matrix_mul(temp,temp,mod);
    }
    return res;
} 

整除和取余Exact division&Remainder

整除

这里不做过多介绍,大家了解整除符号即可。
定义:设 a , b ∈ Z , a ≠ 0 a,b∈Z,a≠0 abZa̸=0,如果存在 q ∈ Z q∈Z qZ使得 b = a q b=aq b=aq,那么就说 b b b可被 a a a整除,记做 a ∣ b a|b ab,且称 b b b a a a的倍数, a a a b b b的约数(也称为除数、因数)。

取余

取余有两套公式,对应乘法与除法。一个整式取模等于每一项因子的取模之和/积再取模。
1. ( a + b ) m o d p = ( a m o d p + b m o d p ) m o d p 1.(a+b) modp=(a modp+bmod p) modp 1.(a+b)modp=(amodp+bmodp)modp
2. ( a × b ) m o d p = ( a m o d p × b m o d p ) m o d p 2.(a×b) modp=(a modp×bmod p) modp 2.(a×b)modp=(amodp×bmodp)modp

关于gcd和lcm

算法不多做介绍了,直接上两题:
练习题:两仪剑法
练习题:取石子游戏

欧拉函数φ(x)与积性函数Euler&Multiplicative

欧拉函数φ(x)

φ ( x ) = x ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) ⋯ ( 1 − 1 p n ) φ(x)=x(1-\frac{1}{p_1})(1-\frac{1}{p_2})\cdots(1-\frac{1}{p_n}) φ(x)=x(1p11)(1p21)(1pn1)
其中 p 1 , p 2 ⋯ p n p_1,p_2\cdots p_n p1p2pn x x x的所有质因数。
欧拉函数还可以理解为i从1~n 满足 gcd(i,n) = 1 的个数

积性函数

欧拉函数 p ( n ) p(n) p(n)为积性函数,但不是完全积性函数。
欧拉函数有以下几个性质:
1、 φ ( p ) = p − 1 φ(p)=p-1 φ(p)=p1
2、 φ ( p k ) = p k − p k − 1 = ( p − 1 ) p k − 1 φ(p^k)=p^k-p^{k-1}=(p-1)p^{k-1} φ(pk)=pkpk1=(p1)pk1
3、若 m , n m,n m,n互质,则有 φ ( m n ) = φ ( m ) φ ( n ) φ(mn)=φ(m)φ(n) φ(mn)=φ(m)φ(n)

  • 质数与质数间一定互质
  • 合数与质数间、合数与合数间可能互质

欧拉定理

a , n a,n a,n互质,则有 a φ ( n ) ≡ 1 ( m o d n ) a^{φ(n)}≡1(modn) aφ(n)1(modn)

费马小定理

a p − 1 = 1 ( m o d p ) a^{p-1}=1(mod p) ap1=1(modp),其中 p p p为质数, a a a为任意正整数。

其他性质

∑ d ∣ n φ ( d ) = n \sum_{d|n}φ(d) = n dnφ(d)=n
意思是, 1 1 1~ n n n中,满足 d ∣ n d|n dn(即 n ≡ 0 ( M O D d ) n≡0(MODd) n0(MODd))的所有 d d d的欧拉函数之和就等于 n n n
貌似跟莫比乌斯反演有着一定的联系。

原根primitive root

当对一个数字 w w w套欧拉两次,得出的结果极为原根的个数。
N = φ ( φ ( w ) ) N = φ(φ(w)) N=φ(φ(w))
求原根目前的做法只能是从2开始枚举,然后暴力判断g^(P-1) = 1 (mod P)是否当且仅当指数为P-1的时候成立。而由于原根一般都不大,所以可以暴力得到。
原根跟快速数论变换有关(NTT),后文有介绍。
关于原根的定义请见百度百科

求原根

long long a[100005], len;
long long q_pow(long long a, long long b, long long c)
{
    long long ans=1;
    while(b)
    {
        if(b%2)
            ans=(ans*a)%c;
        a=(a*a)%c;
        b/=2;
    }
    return ans;
}

// test if g ^ ((p-1)/a) == 1 (mod p)
long long g_test(long long g, long long p)
{
    for(int i=0;i<len;i++)
        if(q_pow(g, (p-1)/a[i], p)==1)
            return 0;
    return 1;
}

long long primitive_root(long long p)
{
    // get the prime factor of p-1
    len=0;
    long long tmp=p-1;
    for(long long i=2;i<=tmp/i;i++)
    {
        if(tmp%i==0)
        {
            a[len++]=i;
            while(tmp%i==0)
                tmp/=i;
        }
    }
    if(tmp!=1)
        a[len++]=tmp;

    // find the primitive root
    long long g=1;
    while(g<p)
    {
        if(g_test(g,p))
            return g;
        g++;
    }
}
}
int main()
{
    getPrime();
    int T;
    int P;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&P);
        solve(P);
    }
    return 0;
}

线性筛(同时得到欧拉函数以及素数表)

代码:

#include
const int MAXN = 10000000;
bool check[MAXN+10];
int phi[MAXN+10];   ///φ
int prime[MAXN+10];
int p;//素数的个数
void phi_and_prime_table(int N){
    memset(check,false,sizeof(check));
    phi[1] = 1;
    tot = 0;
    for(int i = 2; i <= N; i++)
    {
        if( !check[i] )
        {
            prime[++p] = i;
            phi[i] = i-1;
        }
        for(int j = 1; j <= p&&i * prime[j]<=N; j++)
        {
            check[i * prime[j]] = true;
            if( i % prime[j] == 0)
            {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
            else
            {
                phi[i * prime[j]] = phi[i] * (prime[j] - 1);
            }
        }
    }
}

关于质数Prime

质数检测法

算法比较简单,入门同学应该都知道算法优化的逻辑。直接上代码:

int is_prime(int n) {
    for (int i = 2; i * i <= n; ++i) {
        if (n % i == 0) {
            return 0; // 不是质数
        }
    }
    return 1; // 是质数
}

埃式筛法

算法思想可以百度找到,也比较简单,用于筛出一个区间段的所有素数。复杂度小于 O ( n l o g n ) O(nlogn) O(nlogn)大于 O ( n ) O(n) O(n)
代码:

for (int i = 2; i <= n; ++i) {
    is_prime[i] = 1;
}
for (int i = 2; i * i <= n; ++i) {
    if (is_prime[i]) {
        for (int j = i * i; j <= n; j +=i) {
             is_prime[j] = 0;
        }
    }
}

欧式筛法

着重介绍一下这个筛法。它在埃式筛法的基础上,利用了合数最小质因子唯一的思想,只用最小质因子去筛合数,跳过其他因子重复筛选的步骤,从而实现 O ( N ) O(N) O(N)。(不过空间复杂度会比埃式稍大一些)
下面贴代码:

int EulerSieve(int n)
{
    int p=0;
    //memset(prime,0,sizeof(prime));
    //memset(flag,0,sizeof(false));不需要初始化,不需要重复标记
    for(int i=2; i<=n; i++)
    {
	    //记录质因数i。
        if(flag[i]==0)      ///flag==1代表其为合数
            prime[++p]=i;
	    //筛掉i的倍数(无论i为质数还是合数)
        for(int j=1; j<=p&&i*prime[j]<=n; j++)///每次都把prime有的质因数提取出来筛一遍
        {
            flag[i*prime[j]]=1;

            if(i%prime[j]==0) ///下面着重介绍
                break;
        }
    }
    return p;
}

上面代码结合了埃式筛法,可以说是埃式筛法的进阶版,所以基本看懂代码意思应该没问题。可能难理解的地方在这里:

if(i%prime[j]==0) break;

我们进一步思考,实际上他跳过了下一步 i ∗ p r i m e [ j + 1 ] i*prime[j+1] iprime[j+1]以及下m步(直到循环结束)的 i ∗ p r i m e [ j + m ] i*prime[j+m] iprime[j+m]。那么为什么 i ∗ p r i m e [ j + 1 ] i*prime[j+1] iprime[j+1]代表的和数不需要筛除呢?原因是如果 i i i能被 p r i m e [ j ] prime[j] prime[j]整除,说明 i i i p r i m e [ j ] prime[j] prime[j]的整数倍,即 i = p r i m e [ j ] ∗ k , ( k ∈ N ∗ ) i=prime[j] * k,(k∈N^*) i=prime[j]k,(kN),那么 i ∗ p r i m e [ j + 1 ] i*prime[j+1] iprime[j+1]就可以写成 p r i m e [ j ] ∗ k ∗ p r i m e [ j + 1 ] prime[j] * k * prime[j+1] prime[j]kprime[j+1]。由此可知,一个本应该由 p r i m e [ i ] prime[i] prime[i]来筛选的合数就不应该被 i ∗ p r i m e [ j + 1 ] i * prime[j+1] iprime[j+1] 访问到,而是这个合数应该交给 p r i m e [ j ] prime[j] prime[j]来筛选、跳过不必要的重复筛,那么就实现了 O ( n ) O(n) O(n)筛法。

贴一道综合题

POJ-2689
题解略,上代码:

#include
using namespace std;
const int MAXN=100010;
int prime[MAXN+1] = {0};
int flag[MAXN+1] = {0};
int p;
void EulerSieve(int n)
{
    p=0;
    //memset(prime,0,sizeof(prime));
    //memset(flag,0,sizeof(false));不需要初始化,不需要重复标记
    for(int i=2; i<=n; i++)
    {
	    //记录质因数i。
        if(flag[i]==0)      ///flag==1代表其为合数
            prime[++p]=i;
	    //筛掉i的倍数(无论i为质数还是合数)
        for(int j=1; j<=p&&i*prime[j]<=n; j++)///每次都把prime有的质因数提取出来筛一遍
        {
            flag[i*prime[j]]=1;

            if(i%prime[j]==0) ///下面着重介绍
                break;
        }
    }
    return ;
}
bool notprime[1000010];
int prime2[1000010];
void getPrime2(int L,int R)
{
    memset(notprime,false,sizeof(notprime));
    if(L<2) L=2;    ///不要漏了这个,不然会筛出伪素数
    for(int i=1; i<=p&&(long long)prime[i]*prime[i]<=R; i++)
    {
        int s=L/prime[i]+(L%prime[i]>0);    ///s * prime[i]就能从L开始了
        ///记得s要向上取整
        if(s==1)
            s=2;    ///同样s要≥2
        for(int j=s; (long long)j*prime[i]<=R; j++)
            if((long long)j*prime[i]>=L)
                notprime[j*prime[i]-L]=true;
    }
    prime2[0]=0;
    for(int i=0; i<=R-L; i++)
        if(!notprime[i])
            prime2[++prime2[0]]=i+L;
}
int main()
{
    EulerSieve(MAXN);
    int L,U;
    while(scanf("%d%d",&L,&U)==2)
    {
        getPrime2(L,U);
        if(prime2[0]<2)
            printf("There are no adjacent primes.\n");
        else
        {
            int x1=0,x2=100000000,y1=0,y2=0;
            for(int i=1; i<prime2[0]; i++)
            {
                if(prime2[i+1]-prime2[i]<x2-x1)
                {
                    x1=prime2[i];
                    x2=prime2[i+1];
                }
                if(prime2[i+1]-prime2[i]>y2-y1)
                {
                    y1=prime2[i];
                    y2=prime2[i+1];
                }
            }
            printf("%d,%d are closest, %d,%d are most distant.\n",
                   x1,x2,y1,y2);
        }
    }
}

将一个合数按最小质数依次分解,并统计个数

其实就是得到素数组prime后依次相除即可。可以进行 p r i m e [ i ] ∗ p r i m e [ i ] < = x prime[i]*prime[i]<=x prime[i]prime[i]<=x来进行 O ( n ) O(\sqrt{n}) O(n )优化。因为根据素数判断法的思想,如果1~prime[i]都没有筛出最后的因子,那么这个数一定是个1*temp的质数(或者为1),直接将这个数放到下一行即可。
代码:

#include
const int MAXN=10000;
int prime[MAXN+1];
void getPrime()
{
    memset(prime,0,sizeof(prime));
    for(int i=2; i<=MAXN; i++)
    {
        if(!prime[i])
            prime[++prime[0]]=i;
        for(int j=1; j<=prime[0]&&prime[j]<=MAXN/i; j++)
        {
            prime[prime[j]*i]=1;
            if(i%prime[j]==0)
                break;
        }
    }
}
long long factor[100][2];//0是质因数,1是质因数的个数。
int fatCnt;
int getFactors(long long x) ///返回不同质因数的总个数
{
    fatCnt=1;
    long long tmp=x;
    for(int i=1;prime[i]*prime[i]<=tmp;i++)
    {
        factor[fatCnt][1]=0;
        if(tmp%prime[i]==0)
        {
            factor[fatCnt][0]=prime[i];
            while(tmp%prime[i]==0)
            {
                factor[fatCnt][1]++;
                tmp/=prime[i];
            }
            fatCnt++;
        }
    }
    if(tmp!=1)
    {
        factor[fatCnt][0]=tmp;
        factor[fatCnt++][1]=1;
    }
    return fatCnt;
//比如20=2*2*5,2:factor[0][0]=2,factor[0][1]=2,
}//5:factor[1][0]=1,factor[1][1]=1,fatCnt=2
//******************************************
int main()
{
    getPrime();
    int p = getFactors(8);
    for(int i=1;i<p;i++){
        printf("%d %d\n",factor[i][0],factor[i][1]);
    }
    return 0;
}

Fermat素数测试

参考博客:pi9nc的博客
人们在检验费马小定理(如果一个数是质数p,那么满足 a p − 1 = 1 ( m o d p ) a^{p-1}=1(mod p) ap1=1(modp))的逆命题的时候,发现了 2 340 ≡ 1 ( m o d 341 ) 2^{340} ≡1(mod341) 23401(mod341),但341 = 11 * 31。而后人们发现561, 645, 1105等数都表明当底数a=2的时候费马小定理的逆命题不成立,所以人们把所有能够整除 2 n − 1 − 1 2^{n-1} - 1 2n11合数 n n n称作伪素数。
不满足 2 n − 1 ≡ 1 ( m o d n ) 2^{n-1}≡1(modn) 2n11(modn)的n一定不是素数(2是个例外,要注意这一点);如果满足的话则多半是素数。意思即是, 2 n − 1 ≡ 1 ( m o d n ) 2^{n-1}≡1(modn) 2n11(modn)是一个数n是素数的必要条件。一个比试除法效率更高的素性判断方法出现了:将伪素数表制出来,当 n n n满足 2 n − 1 ≡ 1 ( m o d n ) 2^{n-1}≡1(modn) 2n11(modn)且又不在伪素数表里,就说明 n n n是素数。
质数与伪质数的关系:
数论进阶——kuangbin模板+计蒜客课程指引_第1张图片
之所以这种方法更快,是因为我们可以使用二分法快速计算 2 n − 1 m o d n 2^{n-1}modn 2n1modn的值(快速幂,下文有介绍)。
但我们并不真正需要构造出一个伪素数表。人们发现,一个合数在a=2时通过了测试,在a=3的时候的计算结果却排除了可能。于是,人们将伪素数的定义改为:满足 a n − 1 m o d n = 1 a^{n-1} mod n = 1 an1modn=1的合数 n n n叫做以 a a a为底的伪素数(pseudoprime to base a)。(注意,能够整除底数a的质数的小费马式子结果都为0。
那么选取小于待测数的正整数a作为底数,对其作关于底数a的小费马式子测试,只要一个式子不通过,就把它扔回合数世界中。这就是Fermat素性测试

费马小定理毕竟只是素数判定的一个必要条件.满足费马小定理条件的整数n未必全是素数.有些合数也满足费马小定理的条件.这些合数被称作Carmichael数,前3个Carmichael数是561,1105,1729. Carmichael数是非常少的.在1~100000000范围内的整数中,只有255个Carmichael数.
利用下面的二次探测定理可以对上面的素数判定算法作进一步改进,以避免将Carmichael数当作素数.

Miller_Rabin素数测试算法

二次探测定理优化

Miller和Rabin两个人的工作让Fermat素性测试迈出了革命性的一步,建立了Miller-Rabin素性测试算法。新的测试基于下面的定理:
如果p是素数,x是小于p的正整数,且 x 2 ≡ 1 ( m o d p ) x^2≡1(modp) x21(modp),那么要么 x = 1 x=1 x=1,要么 x = p − 1 x=p-1 x=p1
这是显然的,因为 x 2 ≡ 1 ( m o d p ) x^2≡1(modp) x21(modp) 相当于p能整除 x 2 − 1 x^2-1 x21,也即 p p p能整除 ( x + 1 ) ( x − 1 ) (x+1)(x-1) (x+1)(x1)
p p p整除 ( x + 1 ) (x+1) (x+1)(此时 x = p − 1 x = p - 1 x=p1)或 ( x − 1 ) (x-1) (x1)(此时 x x x只能为 1 1 1)

我们下面来演示一下上面的定理如何应用在Fermat素性测试上。前面说过341可以通过以2为底的Fermat测试,因为 2 340 m o d 341 = 1 2^{340} mod 341=1 2340mod341=1。如果341真是素数的话,那么 2 170 m o d 341 2^{170}mod 341 2170mod341只可能是1或340;当算得 2 170 m o d 341 2^{170} mod 341 2170mod341确实等于1时,我们可以继续查看 2 85 2^{85} 285除以341的结果。我们发现, 2 85 m o d 341 = 32 2^{85} mod 341=32 285mod341=32,这一结果摘掉了341头上的素数皇冠
过程:
对于341这个数字:以a=2作为基数,进行如下测试
b e g i n 2 340 m o d 341 ⇒ 1 b e c a u s e ( 340 m o d 2 = = 0 ) ⇓ g o 2 170 m o d 341 ⇒ 1 b e c a u s e ( 170 m o d 2 = = 0 ) ⇓ g o 2 85 m o d 341 ⇒ 32 e n d begin\\ 2^{340} mod 341\Rightarrow 1\\ because(340mod2==0)\Downarrow go\\ 2^{170}mod 341\Rightarrow 1\\ because(170mod2==0)\Downarrow go\\ 2^{85} mod 341\Rightarrow 32\\ end begin2340mod3411because(340mod2==0)go2170mod3411because(170mod2==0)go285mod34132end

这就是Miller-Rabin素性测试的方法。不断地提取指数n-1中的因子2,把n-1表示成 d ∗ 2 r d*2^r d2r(其中d是一个奇数)。那么我们需要计算的东西就变成了 a d ∗ 2 r a^{d*2^r} ad2r除以n的余数。于是,要么等于1,要么等于n-1(那就是素数,跳出)。 a d ∗ 2 r a^{d*2^r} ad2r如果等于1,定理继续适用于 a d ∗ 2 r − 1 a^{d*2^{r-1}} ad2r1,这样不断开方开下去,直到对于某个i满足 a d ∗ 2 r − i m o d n = n − 1 a^{d*2^{r-i}}modn=n-1 ad2rimodn=n1或者最后指数中的2用完了得到的 a d m o d n = n − 1 或 者 = 1 a^{d}modn=n-1 或者 = 1 admodn=n1=1(满足质数,跳出)。这样,Fermat小定理加强为如下形式

尽可能提取因子2,把 n − 1 n-1 n1表示成 d ∗ 2 r d*2^r d2r,如果n是一个素数,那么 a d m o d n = 1 a^{d}modn = 1 admodn=1,或者存在某个i使得 a d ∗ 2 r − i m o d n = n − 1 a^{d*2^{r-i}}modn=n-1 ad2rimodn=n1 ( 0 < = i < r 0<=i<r 0<=i<r) (注意i可以等于0,这就把 a d m o d n = n − 1 a^{d}modn=n-1 admodn=n1的情况统一到后面去了)

Miller-Rabin素性测试同样是不确定算法,我们把可以通过以a为底的Miller-Rabin测试的合数称作以a为底的强伪素数(strong pseudoprime)。第一个以2为底的强伪素数为2047。第一个以2和3为底的强伪素数则大到1 373 653。
Miller-Rabin算法的代码也非常简单:计算d和r的值(可以用位运算加速,即快速积,快速幂),然后二分计算 a d m o d n a^dmodn admodn的值,最后把它平方r次。
代码:

/*对应hoj 1356 Prime Judge*/
#include 
#define MT 5
using namespace std;
typedef long long ll;
int prime[] = {2, 3, 7, 61, 24251};

inline ll powermod(ll base, ll n, ll mod) {
    ll res = 1;
    while (n) {
        if (n & 1) res = (res%mod)*(base%mod)%mod;
        base = (base%mod)*(base%mod)%mod;
        n >>= 1;
    }
    return res;
}

int TwiceDetect(ll a, ll b, ll mod) {///a是底数,b是p-1,k是模数p,测试费马小定理a^(p-1)modp逆定理
    int t = 0;
    ll x, y;
    while ((b & 1) == 0) {
        b >>= 1;
        t++;
    }
    /// b = d * 2^t;   b = d;
    y = x = powermod(a, b, mod);/// x = y = a^d % n(d是奇数,d*2^t==原本的p-1。这里采用正向迭代的方式)
    ///二次探测定理是反向递归的,当然也可以用如下的正向迭代法去测试
    while (t--) {
        y = x*x%mod;
        if (y == 1 && x != 1 && x != mod - 1)///注意y!=1的时候是不做判断的,即对应
            return 0;///递归时在某一环节x==p-1的情况,对此x开方则无意义,但是迭代的话不能break,只能ignore并继续.
        x = y;///继续向高次迭代,那么至少最后一次应该是等于1的(Fermat小定理)
    }
    return y;
}

bool Miller_Rabin(ll n) {
    int i;
    ll tmp;
    for (i = 0; i < MT; i++) {
        tmp = prime[i];
        if (n == prime[i]) return true;
        if (TwiceDetect(tmp, n - 1, n) != 1)
            break;
    }
    return (i == MT);
}

int main() {
    ll n;
    while (scanf("%lld", &n) == 1) {
        if ((n > 1) && Miller_Rabin(n)) {
            printf("YES\n");
        } else {
            printf("NO\n");
        }
    }
    return 0;
}

技巧

对于大数的素性判断,目前Miller-Rabin算法应用最广泛。一般底数仍然是随机选取,但当待测数不太大时,选择测试底数就有一些技巧了。比如,如果被测数小于4 759 123 141,那么只需要测试三个底数2, 7和61就足够了。当然,你测试的越多,正确的范围肯定也越大。如果你每次都用前7个素数(2, 3, 5, 7, 11, 13和17)进行测试,所有不超过341 550 071 728 320的数都是正确的。如果选用2, 3, 7, 61和24251作为底数,那么 1 0 16 10^{16} 1016内唯一的强伪素数为46 856 248 255 981。这样的一些结论使得Miller-Rabin算法在OI中非常实用。通常认为,Miller-Rabin素性测试的正确率可以令人接受,随机选取k个底数进行测试算法的失误率大概为 4 ( − k ) 4^{(-k)} 4(k)

扩展欧几里得Extra Euclid

扩展欧几里得算法是用来在已知a,b的情况下求解一组特解 x , y x,y x,y,使它们满足等式: a x + b y = g c d ( a , b ) = d ax+by=gcd(a,b)=d ax+by=gcd(a,b)=d (gcd 表示最大公约数,该方程的解一定存在)。
exgcd最后要求出 x , y , d x,y,d x,y,d,d来自exgcd返回的结果。
x = y 1 , y = x 1 − a b × y 1 , d = e x g c d ( ) x=y_1,y=x_1-\frac{a}{b}×y_1,d = exgcd() x=y1,y=x1ba×y1d=exgcd()

int exgcd(int a, int b, int &x, int &y) {
    if(b == 0) {
        x = 1;
        y = 0;
        return a;
    }
    int d = exgcd(b, a % b, x, y);
    int t = x; x = y; y = t - a / b * y;
    return d;
}

那么对于 a x + b y = c ax + by = c ax+by=c这个方程来说,只有 d ∣ c d|c dc的情况下此方程才有解,而通解为 x = c d x 0 + k b d , y = c d y 0 − k a d x=\frac{c}{d}x_0+k\frac{b}{d},y=\frac{c}{d}y_0-k\frac{a}{d} x=dcx0+kdb,y=dcy0kda

乘法逆元

给定两个整数a和p。假设存在一个x使得 a x ≡ 1 ( m o d p ) ax≡1(modp) ax1(modp)。那么我们称 x x x a a a关于 p p p的乘法逆元。对于逆元的求法,可以借助扩展欧几里得,把上面的式子变形一下,变成 a x + k p = 1 ax+kp=1 ax+kp=1。就可以用扩展欧几里得就出一个 x x x。并且,我们也可以发现, a a a关于 p p p的逆元存在的充要条件是 g c d ( a , p ) = 1 gcd(a,p)=1 gcd(ap)=1,也就是 a a a p p p必须互质。
在知乎上看到一种说法,逆元在方程中的作用就是为了消去对应参数的作用,将其置为1,这个作用的理解听上去比较通俗易懂,尚不知是否符合。
那么我们可以这样理解,在modp的环境下,一个数的倒数就可以表示成关于模p的逆元。比如 1 ( n − m ) ! m o d p = i n v [ ( n − m ) ! ] m o d p \frac{1}{(n-m)!}modp = inv[(n-m)!] modp (nm)!1modp=inv[(nm)!]modp那么这时候表示分母就容易许多(因为我们可以在整数域下处理倒数)。

线性预处理逆元inverse

求出 i ( i ∈ 1 − n ) i(i∈1-n) i(i1n)关于 p p p的逆元。
直接上代码:

// p 必须为质数,p / i 为整除。
inv[1] = 1;
for (int i = 2; i <= n; ++i) {
    inv[i] = (p - p / i) * inv[p % i] % p;
}

应用:
求组合数取模的时候可以用到。
f a c t i = 1 ∗ 2 ∗ 3 … i m o d p , i n v s i = i n v 1 ∗ i n v 2 … i n v i m o d p fact_i=1 * 2 * 3…i modp,inv_{s_i}=inv_1*inv_2…inv_imodp facti=123imodpinvsi=inv1inv2invimodp
C n m m o d p = n ! m ! ( n − m ) ! m o d p = f a c t n ⋅ i n v s m ⋅ i n v s n − m m o d p C^{m}_{n}modp =\frac{n!}{m!(n-m)!}modp=fact_n·inv_{s_m}·inv_{s_{n-m}}modp Cnmmodp=m!(nm)!n!modp=factninvsminvsnmmodp
没有题目。一般的扩展欧几里得题目比较容易写出方程,只要注意如何取模至正数的方法,以及边界值0的情况即可。

用费马小定理来求逆元(模数是质数)

逆元的方程: a x ≡ 1 ( m o d p ) ax≡1(modp) ax1(modp)
费马小定理的方程: a p − 1 ≡ 1 ( m o d p ) a^{p-1} ≡ 1(modp) ap11(modp)
发现两者可以联立: a x ≡ a p − 1 ( m o d p ) ax ≡ a^{p-1}(modp) axap1(modp)得到如下方程:
x ≡ a p − 2 ( m o d p ) x ≡ a^{p-2}(modp) xap2(modp)
之后用快速幂便可求得逆元。
(当模数是质数,其实就是欧拉定理的特殊情况)

用欧拉定理求逆元(模不一定是质数)

根据欧拉定理,只要 a , m a,m a,m互质,那么就有 a φ ( m ) ≡ 1 ( m o d m ) a^{φ(m)}≡1(modm) aφ(m)1(modm)将其变形一下,即有 a ∗ a φ ( m ) − 1 ≡ 1 ( m o d m ) a*a^{φ(m)-1}≡1(modm) aaφ(m)11(modm),那么对于a关于p的逆元就是 a φ ( m ) − 1 a^{φ(m)-1} aφ(m)1。线性求出欧拉数,然后套快速幂。

无敌初始化(求出阶乘模,乘法逆元,阶乘逆元)

用来求组合数取模问题。巨强。

void init()
{
	fact[0] = fact[1] = 1;
	fiv[0] = fiv[1] = 1; ///fiv是阶乘逆元
	inv[1] = 1;
	for(int i = 2; i <= maxn; ++i)  
	{
		//递推保存fact阶乘,递推求inv和fiv各个逆元 
	    fact[i] = fact[i-1]*i%mod;
	    inv[i] = (mod-mod/i)*inv[mod%i]%mod;
	    fiv[i] = inv[i]*fiv[i-1]%mod;
	}
}

中国剩余定理(模线性方程组)

《孙子算经》中这样提到:有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?
这句话的意思就是,一个整数除以三余二,除以五余三,除以七余二,问这个整数的值是多少。因为《孙子算经》中首次提出了这种问题并给出了具体的解法,所以中国剩余定理也叫小子定理。

样式

中国剩余定理的一元模线性方程组如下:
S : { x = a 1 ( m o d m 1 ) x = a 2 ( m o d m 2 ) ⋮ x = a n ( m o d m n ) S : \begin{cases} x=a1(mod m1)\\ x=a2(mod m2)\\ \vdots\\ x=an(mod mn)& \end{cases} S:x=a1(modm1)x=a2(modm2)x=an(modmn)

解法:

中国剩余定理定义:假设整数 m 1 , m 2 , … , m n m_1,m_2,…,m_n m1,m2,,mn两两互质,则方程组S一定有解,通解可以构造如下:
M = m 1 × m 2 × … × m n = ∏ i = 1 n m i M=m_1×m_2×…×m_n=\prod_{i=1}^{n}m_i M=m1×m2××mn=i=1nmi
并设 M i = M / m i M_i=M/m_i Mi=M/mi t i = M − 1 t_i=M^{-1} ti=M1,表示 M i M_i Mi m i m_i mi意义下的倒数,
M i ∗ t i ≡ 1 ( m o d m i ) M_i*t_i≡1(modm_i) Miti1(modmi)(即 M i ∗ t i + k ∗ m i = 1 M_i*t_i + k*m_i=1 Miti+kmi=1)。

那么方程式的通解可以写作:
x = a 1 t 1 M 1 + a 2 t 2 M 2 + … + a n t n M n + k M = ∑ i = 1 n a i t i M i + k M x=a_1t_1M_1+a_2t_2M_2+…+a_nt_n M_n+kM=\sum_{i=1}^{n}a_it_iM_i+kM x=a1t1M1+a2t2M2++antnMn+kM=i=1naitiMi+kM

从方程式中我们可以知道,唯一没有确定下来的参数是 t i t_i ti,而根据恒等式 M i ∗ t i ≡ 1 ( m o d m i ) M_i*t_i≡1(modm_i) Miti1(modmi),我们可以得出等式 M i ∗ t i + k ∗ m i = 1 M_i*t_i + k*m_i=1 Miti+kmi=1,那么我们就根据扩展欧几里得算法将 t i t_i ti求出。

代码:(前提是模数两两互质)

int CRT(int a[], int m[], int n) {
    int M = 1;
    int ans = 0;
    for(int i = 1; i <= n; i++) {
        M *= m[i];
    }
    for(int i = 1; i <= n; i++) {
        int x, y;
        int Mi = M / m[i];
        exgcd(Mi, m[i], x, y);
        ans = (ans + Mi * x * a[i]) % M;///记得要取模,这是在取模的条件下得出的t_i(t_i即为x)
    }
    if(ans < 0) ans += M;
    return ans;
}

不互质模方程组模板:(因为我也没研究明白,就不讲了)
代码:

/**
中国剩余定理(不互质)
*/
#include 
#include 
#include 
using namespace std;

int Mod;

int gcd(int a, int b)
{
    if(b==0)
        return a;
    return gcd(b,a%b);
}

int exgcd(int a, int b, int&x, int& y)
{
    if(b==0)
    {
        x=1,y=0;
        return a;
    }
    int d = exgcd(b,a%b,x,y);
    int t = x;
    x = y;
    y = t - a/b*y;
    return d;
}

//a在模n乘法下的逆元,没有则返回-1
int inv(int a, int n)
{
    int x,y;
    int t = exgcd(a,n,x,y);
    if(t != 1)
        return -1;
    return (x%n+n)%n;
}

//将两个方程合并为一个
bool merge(int a1, int n1, int a2, int n2, int& a3, int& n3)
{
    int d = gcd(n1,n2);
    int c = a2-a1;
    if(c%d)
        return false;
    c = (c%n2+n2)%n2;
    c /= d;
    n1 /= d;
    n2 /= d;
    c *= inv(n1,n2);
    c %= n2;
    c *= n1*d;
    c += a1;
    n3 = n1*n2*d;
    a3 = (c%n3+n3)%n3;
    return true;
}

//求模线性方程组x=ai(mod ni),ni可以不互质
int China_Remander2(int len, int* a, int* n)
{
    int a1=a[0],n1=n[0];
    int a2,n2;
    for(int i = 1; i < len; i++)
    {
        int aa,nn;
        a2 = a[i],n2=n[i];
        if(!merge(a1,n1,a2,n2,aa,nn))
            return -1;
        a1 = aa;
        n1 = nn;
    }
    Mod = n1;
    return (a1%n1+n1)%n1;
}
int a[1000],b[1000];
int main()
{
    int i;
    int k;
    while(scanf("%d",&k)!=EOF)
    {
        for(i = 0; i < k; i++)
            scanf("%d %d",&a[i],&b[i]); ///这里a是模数,b是余数
        printf("%d\n",China_Remander2(k,b,a));
    }
    return 0;
}

高斯消元法Gauss elimination

这篇博客写的很好pengwill97的博客,我觉得比我要讲要好很多哈哈哈。不懂高斯消元法的原理可以看这篇博客。
浮点数代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

///浮点型高斯消元模板
const double eps=1e-9;
const int maxm=1000;///m个方程,n个变量
const int maxn=1000;
int m,n;
double a[maxm][maxn+1];///增广矩阵
bool free_x[maxn];///判断是否是不确定的变元
double x[maxn];///解集

int sign(double x)
{
    return (x>eps)-(x<-eps);
}
/**返回值:
-1 无解
0 有且仅有一个解
>=1 有多个解,根据free_x判断哪些是不确定的解
*/
int Gauss()
{
    int i,j;
    int row,col,max_r;
    m=n;///n个方程,n个变量的那种情况
    for(row=0,col=0; row<m&&col<n; row++,col++)
    {
        max_r=row;
        for(i=row+1; i<m; i++)   ///找到当前列所有行中的最大值(做除法时减小误差)
        {
            if(sign(fabs(a[i][col])-fabs(a[max_r][col]))>0)
                max_r=i;
        }
        if(max_r!=row)
        {
            for(j=row; j<n+1; j++)
                swap(a[max_r][j],a[row][j]);
        }
        if(sign(a[row][col])==0)   ///当前列row行以下全为0(包括row行)
        {
            row--;
            continue;
        }
        for(i=row+1; i<m; i++)
        {
            if(sign(a[i][col])==0)
                continue;
            double tmp=a[i][col]/a[row][col];
            for(j=col; j<n+1; j++)
                a[i][j]-=a[row][j]*tmp;
        }
    }
    for(i=row; i<m; i++)   ///col=n存在0...0,a的情况,无解
    {
        if(sign(a[i][col]))
            return -1;
    }
    if(row<n)   ///存在0...0,0的情况,有多个解,自由变元个数为n-row个
    {
        for(i=row-1; i>=0; i--)
        {
            int free_num=0;///自由变元的个数
            int free_index = 0;///自由变元的序号
            for(j=0; j<n; j++)
            {
                if(sign(a[i][j])!=0&&free_x[j])
                    free_num++,free_index=j;
            }
            if(free_num>1)
                continue;///该行中的不确定的变元的个数超过1个,无法求解,它们仍然为不确定的变元
            ///只有一个不确定的变元free_index,可以求解出该变元,且该变元是确定的
            double tmp=a[i][n];
            for(j=0; j<n; j++)
            {
                if(sign(a[i][j])!=0&&j!=free_index)
                    tmp-=a[i][j]*x[j];
            }
            x[free_index]=tmp/a[i][free_index];
            free_x[free_index]=false;
        }
        return n-row;
    }
    ///有且仅有一个解,严格的上三角矩阵(n==m)
    for(i=n-1; i>=0; i--)
    {
        double tmp=a[i][n];
        for(j=i+1; j<n; j++)
            if(sign(a[i][j])!=0)
                tmp-=a[i][j]*x[j];
        x[i]=tmp/a[i][i];
    }
    return 0;
}///模板结束

int t,xx;

int main()
{
    //freopen("in.txt","r",stdin);
    cin>>t;
    while(t--)
    {
        ///m个方程,n个变量
        scanf("%d%d",&m,&n);
        int q = m;
        int ans = n;
        for(int i = 0; i<m; i++)
            for(int j = 0; j<=n; j++)
                scanf("%lf",&a[i][j]);
        int ret = Gauss();
        if(ret==-1)
            puts("无解");
        else if(ret==0)
        {
            bool flag = true;
            for(int i = 0; i<q; i++)
            {
                double sum = 0;
                for(int j = 0; j<ans; j++)
                    sum+=a[i][j]*x[j];
                if(fabs(sum-a[i][ans])>eps)
                {
                    flag = false;
                    break;
                }
            }
            if(flag)
            {
                puts("Only result:");
                for(int i = 0; i<ans; i++)
                    printf("x%d:%.4f\n",i+1,x[i]);
            }
            else
                puts("No result./n");
        }
        else
            puts("infinity results.\n");
    }
    return 0;
}

网上有用高斯消元法解决开关问题的。我个人目前遇到的开关问题,都能用状态压缩dp中的滚动dp和子集dp完成题目。如果有兴趣了解开关问题怎样构建方程的,可以看kuangbin写的poj-1681的题解。这里就不贴出来了。

高斯消元处理同余方程组

多元一次同余方程的写法: a 0 + a 1 x 1 + a 2 x 2 + . . . a n − 1 x n − 1 ≡ b ( m o d m ) a_0+a_1x_1+a_2x_2+...a_{n-1}x_{n-1}≡b(modm) a0+a1x1+a2x2+...an1xn1b(modm)
记不记得同余方程的定义?其实就是恒等式两方同时取模后的值是相等的。其实只需要在高斯消元的基础上逐项取个模即可。
代码:
观察下面取模的地方。

#include
using namespace std;
//求解对 MOD 取模的方程组
const int MOD = 7;
const int MAXN = 400;
int a[MAXN][MAXN];//增广矩阵
int x[MAXN];//最后得到的解集
inline int gcd(int a,int b)
{
    while(b != 0)
    {
        int t = b;
        b = a%b;
        a = t;
    }
    return a;
}
inline int lcm(int a,int b)
{
    return a/gcd(a,b)*b;
}
long long inv(long long a,long long m)///inv=inverse逆元
{
    if(a == 1)
        return 1;
    return inv(m%a,m)*(m-m/a)%m;
}
///这个gauss模变量系数算是模板了
int Gauss(int equ,int var)///equ=equation等式。var=variable变量
{
    int max_r,col,k;
    for(k = 0, col = 0; k < equ && col < var; k++,col++)
    {
        max_r = k;
        for(int i = k+1; i < equ; i++)
            if(abs(a[i][col]) > abs(a[max_r][col]))
                max_r = i;///当前列中绝对值最大的行坐标,记作max_r
        if(a[max_r][col] == 0)
        {
            k--;
            continue;
        }
        if(max_r != k)
            for(int j = col; j < var+1; j++)
                swap(a[k][j],a[max_r][j]);
        for(int i = k+1; i < equ; i++)
        {
            if(a[i][col] != 0)
            {
                int LCM = lcm(abs(a[i][col]),abs(a[k][col]));
                int ta = LCM/abs(a[i][col]);
                int tb = LCM/abs(a[k][col]);
                if(a[i][col]*a[k][col] < 0)
                    tb = -tb;
                for(int j = col; j < var+1; j++)
                    a[i][j] = ((a[i][j]*ta - a[k][j]*tb)%MOD + MOD)%MOD;
            }///对变量系数方进行取模
        }
    }
    for(int i = k; i < equ; i++)
        if(a[i][col] != 0)
            return -1;//无解
    if(k < var)
        return var-k;//多解
    for(int i = var-1; i >= 0; i--)
    {
        int temp = a[i][var];
        for(int j = i+1; j < var; j++)
        {
            if(a[i][j] != 0)
            {
                temp -= a[i][j]*x[j];
                temp = (temp%MOD + MOD)%MOD;
            }
        }
        x[i] = (temp*inv(a[i][i],MOD))%MOD;
    }
    return 0;
}
int change(char s[])
{
    if(strcmp(s,"MON") == 0)
        return 1;
    else if(strcmp(s,"TUE")==0)
        return 2;
    else if(strcmp(s,"WED")==0)
        return 3;
    else if(strcmp(s,"THU")==0)
        return 4;
    else if(strcmp(s,"FRI")==0)
        return 5;
    else if(strcmp(s,"SAT")==0)
        return 6;
    else
        return 7;
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m) == 2)
    {
        if(n == 0 && m == 0)
            break;
        memset(a,0,sizeof(a));
        char str1[10],str2[10];
        int k;
        for(int i = 0; i < m; i++)
        {
            scanf("%d%s%s",&k,str1,str2);
            a[i][n] = ((change(str2) - change(str1) + 1)%MOD + MOD)%MOD;
            int t;
            while(k--)
            {
                scanf("%d",&t);
                t--;
                a[i][t] ++;
                a[i][t]%=MOD;
            }
        }
        int ans = Gauss(m,n);
        if(ans == 0)
        {
            for(int i = 0; i < n; i++)
                if(x[i] <= 2)
                    x[i] += 7;
            for(int i = 0; i < n-1; i++)
                printf("%d ",x[i]);
            printf("%d\n",x[n-1]);
        }
        else if(ans == -1)
            printf("Inconsistent data.\n");
        else
            printf("Multiple solutions.\n");
    }
    return 0;
}

傅里叶变换&&数论变换Fourier transform

离散傅里叶变换(DFT)&&快速傅里叶变换(FFT)

参考了此博客:胡小兔的OI博客
这篇博客写的很好了,我觉得我写不出这样的高度。不过在此留下两点要点:

  • 1、题目的条件或暗含条件都是给了 a 0 到 a n − 1 a_0到a_{n-1} a0an1的,这个不是你要求的东西。
  • 2、现将式子构造成点值函数,再将式子变回向量 a 0 . a 1 , . . . , a n − 1 {a_0.a_1,...,a_{n-1}} a0.a1,...,an1形式。求解过程已经涉及了傅里叶变换和傅里叶逆变换了。

那么这里贴上一个十进制的高精度乘法 ( n l o g 2 n ) (nlog_2n) (nlog2n)。其实对于一些高难度的傅里叶题目,做不出来也没问题,因为需要一定的数学基础。但这个高精度乘法代表的算法实质已经很明了了。就算不清楚递归与蝴蝶变换,只要理解了原理,然后在题目中又能够造出多项式,那么就要考虑来一个FFT了。

#include 
#include 
#include 
#include 
#include 
#define space putchar(' ')
#define enter putchar('\n')
using namespace std;
typedef long long ll;
/*不必要的时候就不用了
template 
void read(T &x){
    char c;
    bool op = 0;
    while(c = getchar(), c < '0' || c > '9')
    if(c == '-') op = 1;
        x = c - '0';
    while(c = getchar(), c >= '0' && c <= '9')
        x = x * 10 + c - '0';
    if(op) x = -x;
}
template 
void write(T x){
    if(x < 0) putchar('-'), x = -x;
    if(x >= 10) write(x / 10);
    putchar('0' + x % 10);
}
*/
const int N = 1000005;
const double PI = acos(-1);
typedef complex <double> cp;
char sa[N], sb[N];
int n = 1, lena, lenb, res[N];
cp a[N], b[N], omg[N], inv[N];
void init(){
    for(int i = 0; i < n; i++){
        omg[i] = cp(cos(2 * PI * i / n), sin(2 * PI * i / n));
        inv[i] = conj(omg[i]); ///conj求omg的共轭复数
    }
}
void fft(cp *a, cp *omg){
    int lim = 0;
    while((1 << lim) < n) lim++;
    for(int i = 0; i < n; i++){
        int t = 0;
        for(int j = 0; j < lim; j++)
            if((i >> j) & 1) t |= (1 << (lim - j - 1));
        if(i < t) swap(a[i], a[t]); // i < t 的限制使得每对点只被交换一次(否则交换两次相当于没交换)
    }
    for(int l = 2; l <= n; l *= 2){
        int m = l / 2;
   	    for(cp *p = a; p != a + n; p += l){
	        for(int i = 0; i < m; i++){
	            cp t = omg[n / l * i] * p[i + m];
	            p[i + m] = p[i] - t;
	            p[i] += t;
	        }
        }
    }
}
int main(){
    scanf("%s%s", sa, sb);
    lena = strlen(sa), lenb = strlen(sb);
    while(n < lena + lenb) n<<=1;   
    ///lena+lenb指的是卷积后的总长度。我们挑选的n应该是2^i同时要大于卷积长
    for(int i = 0; i < lena; i++)
        a[i].real(sa[lena - 1 - i] - '0');///读到的第一位数是最高位的
    for(int i = 0; i < lenb; i++)
        b[i].real(sb[lenb - 1 - i] - '0');
    init();
    fft(a, omg);
    fft(b, omg);        ///依次对两个函数进行FFT
    for(int i = 0; i < n; i++)
        a[i] *= b[i];   ///让两个快速傅里叶转换数的系数依次相乘。鉴于C(x) = A(x) * B(x)///为卷积做好准备
    fft(a, inv);        ///对最后一个傅里叶数进行共轭转换。乘omega的倒数,即为其共轭复数
    for(int i = 0; i < n; i++){
        res[i] += floor(a[i].real() / n + 0.5);
        res[i + 1] += res[i] / 10;
        res[i] %= 10;
    }
    ///res就是最后的卷积结果,他来自于傅里叶转换的实部
    ///得到res再按顺序输出的话,相当于对每个系数都乘了对应的10^k,相当于带了x=10进去
    for(int i = res[lena + lenb - 1] ? lena + lenb - 1: lena + lenb - 2; i >= 0; i--)
        putchar('0' + res[i]);
    enter;
    return 0;
}

高精度乘法作用的卷积就是每个对应位置的数。比如 12345 12345 12345中的 2 2 2,它就代表在在千位数上出现了两次,最后我们将它归到 a 3 a_3 a3 a i a_i ai从零开始第四位)就应该等于2。相当于数与数之间有x的高阶差。

再贴一道,kuangbin的HDU 4609,1e5个木棒,任取三根组成三角形的概率。
题以及题解:kuangbin的博客题解
(求组合(以和的形式)的个数,考虑用卷积,然后用傅里叶转换来实现)
代码:

#include 
#include 
#include 
#include 
using namespace std;

typedef complex<double> cp;
const double PI = acos(-1.0);

void change(cp y[],int len)
{
    int i,j,k;
    for(i = 1, j = len/2;i < len-1;i++)
    {
        if(i < j)swap(y[i],y[j]);
        k = len/2;
        while( j >= k)
        {
            j -= k;
            k /= 2;
        }
        if(j < k)j += k;
    }
}
void fft(cp y[],int len,int on)
{
    change(y,len);
    for(int h = 2;h <= len;h <<= 1)
    {
        cp wn(cos(-on*2*PI/h),sin(-on*2*PI/h));
        for(int j = 0;j < len;j += h)
        {
            cp w(1,0);
            for(int k = j;k < j+h/2;k++)
            {
                cp u = y[k];
                cp t = w*y[k+h/2];
                y[k] = u+t;
                y[k+h/2] = u-t;
                w = w*wn;
            }
        }
    }
    if(on == -1)
        for(int i = 0;i < len;i++)
            y[i] /= len;
}

const int MAXN = 400040;
cp x1[MAXN];
int a[MAXN/4];
long long num[MAXN];//100000*100000会超int
long long sum[MAXN];

int main()
{
    int T;
    int n;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        memset(num,0,sizeof(num));
        for(int i = 0;i < n;i++)
        {
            scanf("%d",&a[i]);
            num[a[i]]++;
        }
        sort(a,a+n);
        int len1 = a[n-1]+1;
        int len = 1;
        while( len < 2*len1 )len <<= 1;
        for(int i = 0;i < len1;i++)
            x1[i] = cp(num[i],0);
        for(int i = len1;i < len;i++)
            x1[i] = cp(0,0);
        fft(x1,len,1);
        for(int i = 0;i < len;i++)
            x1[i] = x1[i]*x1[i];
        fft(x1,len,-1);
        for(int i = 0;i < len;i++)
            num[i] = (long long)(x1[i].real()+0.5);
        len = 2*a[n-1];
        //减掉取两个相同的组合
        for(int i = 0;i < n;i++)
            num[a[i]+a[i]]--;
        //选择的无序,除以2
        for(int i = 1;i <= len;i++)
        {
            num[i]/=2;
        }
        sum[0] = 0;
        for(int i = 1;i <= len;i++)
            sum[i] = sum[i-1]+num[i];
        long long cnt = 0;
        for(int i = 0;i < n;i++)
        {
            cnt += sum[len]-sum[a[i]];
            //减掉一个取大,一个取小的
            cnt -= (long long)(n-1-i)*i;
            //减掉一个取本身,另外一个取其它
            cnt -= (n-1);
            //减掉大于它的取两个的组合
            cnt -= (long long)(n-1-i)*(n-i-2)/2;
        }
        //总数
        long long tot = (long long)n*(n-1)*(n-2)/6;
        printf("%.7lf\n",(double)cnt/tot);
    }
    return 0;
}

快速数论变换(NTT)

FFT毕竟是在复数领域上作用的,对于一些整数类型用傅里叶做卷积的话会有精度损失。那么对于整数做卷积的题目,那么这时候就应该使用NTT了。(关于模数的选取用下面的P,或者P=998244353也没问题)
上个高精度乘法取模的代码:

#include 
#include 
#include 
 
using namespace std;
typedef long long LL;
 
const int N = 1 << 18;
const int P = (479 << 21) + 1;///这里的模数只是用于做数论卷积的而已,不是最后的数取模。最后的取模可以最后long long输出,while(...) num = (num*10 + a[i])取模,这个是模的性质,跟NTT没有关系
const int G = 3;
const int NUM = 20;
 
LL  wn[NUM];
LL  a[N], b[N];
char A[N], B[N];
 
LL quick_mod(LL a, LL b, LL m)
{
    LL ans = 1;
    a %= m;
    while(b)
    {
        if(b & 1)
        {
            ans = ans * a % m;
            b--;
        }
        b >>= 1;
        a = a * a % m;
    }
    return ans;
}
 
void GetWn()
{
    for(int i = 0; i < NUM; i++)
    {
        int t = 1 << i;
        wn[i] = quick_mod(G, (P - 1) / t, P);
    }
}
 
void Prepare(char A[], char B[], LL a[], LL b[], int &len)
{
    len = 1;
    int L1 = strlen(A);
    int L2 = strlen(B);
    while(len <= 2 * L1 || len <= 2 * L2) len <<= 1;
    for(int i = 0; i < len; i++)
    {
        if(i < L1) a[i] = A[L1 - i - 1] - '0';
        else a[i] = 0;
        if(i < L2) b[i] = B[L2 - i - 1] - '0';
        else b[i] = 0;
    }
}
 
void Rader(LL a[], int len)
{
    int j = len >> 1;
    for(int i = 1; i < len - 1; i++)
    {
        if(i < j) swap(a[i], a[j]);
        int k = len >> 1;
        while(j >= k)
        {
            j -= k;
            k >>= 1;
        }
        if(j < k) j += k;
    }
}
 
void NTT(LL a[], int len, int on)
{
    Rader(a, len);
    int id = 0;
    for(int h = 2; h <= len; h <<= 1)
    {
        id++;
        for(int j = 0; j < len; j += h)
        {
            LL w = 1;
            for(int k = j; k < j + h / 2; k++)
            {
                LL u = a[k] % P;
                LL t = w * a[k + h / 2] % P;
                a[k] = (u + t) % P;
                a[k + h / 2] = (u - t + P) % P;
                w = w * wn[id] % P;
            }
        }
    }
    if(on == -1)
    {
        for(int i = 1; i < len / 2; i++)
            swap(a[i], a[len - i]);
        LL inv = quick_mod(len, P - 2, P);
        for(int i = 0; i < len; i++)
            a[i] = a[i] * inv % P;
    }
}
 
void Conv(LL a[], LL b[], int n)
{
    NTT(a, n, 1);
    NTT(b, n, 1);
    for(int i = 0; i < n; i++)
        a[i] = a[i] * b[i] % P;
    NTT(a, n, -1);
}
 
void Transfer(LL a[], int n)
{
    int t = 0;
    for(int i = 0; i < n; i++)
    {
        a[i] += t;
        if(a[i] > 9)
        {
            t = a[i] / 10;
            a[i] %= 10;
        }
        else t = 0;
    }
}
 
void Print(LL a[], int n)
{
    bool flag = 1;
    for(int i = n - 1; i >= 0; i--)
    {
        if(a[i] != 0 && flag)
        {
            //使用putchar()速度快很多
            putchar(a[i] + '0');
            flag = 0;
        }
        else if(!flag)
            putchar(a[i] + '0');
    }
    puts("");
}
 
int main()
{
    GetWn();
    while(scanf("%s %s", A, B) != EOF)
    {
        int len;
        Prepare(A, B, a, b, len);
        Conv(a, b, len);
        Transfer(a, len);
        Print(a, len);
    }
    return 0;
}

母函数(生成函数)Generating function

刚开始学习母函数的时候,对于多项式乘积来表示组合情况的时候,我以为是傅里叶变换,但对于傅里叶转换应用基本局限在一重组合***甚至不用傅里叶*** (比如给你几个数,问你有多少种,例如 1 , 3 , 4 , 4 , 4 1,3,4,4,4 1,3,4,4,4,一共有三类( 1 , 3 , 4 1,3,4 1,3,4))和二重组合(给你几个数,再给你另一个集合的几个数,让你求组合(相加)后有几种情况,比如{ 1 , 2 , 3 1,2,3 1,2,3}和{ 4 , 5 , 5 4,5,5 4,5,5},他们组合就有{5,6,7,8}四种情况)。对于二重组合来说,FFT可以将算法优化到 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)级别,在二重组合上很是优秀。但是在解决多重组合上面就不行了。

(我又双找了别人的博客,没办法写得好啊哈哈哈哈)这片博客介绍了…

把要点提炼出来,即为:

  • 1、指数对应种类序列,系数对应计数序列
  • 2、根据第一点,我们可以知道观察问题的流程:①关注问题的种类序列②其对应的计数序列要写全
  • 3、母函数是计数工具,x的取值我们不关心,似乎只是个占位置的东西

引入母函数的目的,其实是给了我们将复杂问题映射为简单问题的多一种思路(尤其是对于组合问题来说)。而**多项式的乘法运算(系数相乘,指数相加)**使母函数具有了计数能力。

其实母函数是可以用背包 d p dp dp来实现的。但它(母函数)作为最基础而优雅的暴力解法,它通常能给背包 d p dp dp提供最初始的思路,而背包 d p dp dp算法要做的就是优化这个暴力思路,包括空间和时间上。

比如整数拆分题:HDU-1028,就可以用以下的母函数代码做:

#include
using namespace std;
//  母函数
const int maxn=120;
int f[maxn+1]={0};        //  保存对应指数的系数(方案数)
int tmp[maxn+1]={0};      //  临时保存分解一个因式的对应指数的系数
void faction(int N)
{
    for(int i=0;i<=N;i++)
        f[i]=1;     //  第一个数,所构成的方案数都是1;
    for(int i=2;i<=N;i++){      //  从第二个表达式开始
        //  因式逐个分解;
        //第i个表达式的质数递增速率是i
        for(int j=0;j<=N;j++)  //  表达式内从第一个开始(j表示的是指数);//前面已经相乘的表达式要遍历一次
            for(int k=0;k+j<=N;k+=i)   //  表达式指数增加值;
                tmp[j+k]+=f[j]; //  表示指数为j+k的系数;
        //  更新指数系数;
        //两个for循环代表两个多项式做笛卡尔积
        for(int j=0;j<=N;j++){
            f[j]=tmp[j];    ///新的多项式
            tmp[j]=0;
        }
    }
}
///整个过程就是最左边的多项式乘上第二左的多项式
///然后得到的新多项式作为最左侧的多项式
///同时我们只需关注前n个,因为后面的笛卡尔积得到的结果(指数)肯定是大于n的
int main()
{
    int n,N;
    cin.sync_with_stdio(false);
    faction(maxn);
    while(cin>>n){
        cout<<f[n]<<endl;
    }
    return 0;
}

有个跟编程无关,但很有意思的推理:斐波那契求通项。
斐波那契通项公式
下面我们用生成函数求斐波那契数列的通项公式:

首先 f ( x ) = x + x 2 + 2 x 3 + 3 x 4 + 5 x 5 + . . . f(x)=x+x^2+2x^3+3x^4+5x^5+... f(x)=x+x2+2x3+3x4+5x5+...

f ( x ) f(x) f(x)乘上个 x x x,然后相减

f ( x ) − x ∗ f ( x ) = ( x + x 2 + 2 x 3 + 3 x 4 + . . . ) − ( x 2 + x 3 + 2 x 4 + 3 x 5 + . . . ) = x + x 3 + x 4 + 2 x 5 + 3 x 6 + . . . = x + x 2 f ( x ) f(x)−x∗f(x)=(x+x^2+2x^3+3x^4+...)-(x^2+x^3+2x^4+3x^5+...)=x+x^3+x^4+2x^5+3x^6+...=x+x^2f(x) f(x)xf(x)=(x+x2+2x3+3x4+...)(x2+x3+2x4+3x5+...)=x+x3+x4+2x5+3x6+...=x+x2f(x)

f ( x ) f(x) f(x) f ( x ) = x 1 − x − x 2 f(x)=\frac{x}{1-x-x^2} f(x)=1xx2x

然后如何还原成序列呢?

先因式分解

x 1 − x − x 2 = x ( 1 − 1 − 5 2 x ) ( 1 − 1 + 5 2 x ) \frac{x}{1-x-x^2}=\frac{x}{(1-\frac{1-\sqrt{5}}{2}x)(1-\frac{1+\sqrt{5}}{2}x)} 1xx2x=(1215 x)(121+5 x)x

用裂项法 1 n ( n + k ) = 1 k ( 1 n − 1 n + k ) \frac{1}{n(n+k)}=\frac{1}{k}(\frac{1}{n}-\frac{1}{n+k}) n(n+k)1=k1(n1n+k1)

x ( 1 − 1 − 5 2 x ) ( 1 − 1 + 5 2 x ) = 1 ( 1 − 1 − 5 2 x ) ( ( 1 − 1 − 5 2 x + ( − 5 x ) ) x = 1 − 5 ( 1 1 − 1 − 5 2 x − 1 1 − 1 + 5 2 x ) = − 1 5 1 1 − 1 − 5 2 x + 1 5 1 1 − 1 + 5 2 x \frac{x}{(1-\frac{1-\sqrt{5}}{2}x)(1-\frac{1+\sqrt{5}}{2}x)}\\ \large=\frac{1}{(1-\frac{1-\sqrt{5}}{2}x)((1-\frac{1-\sqrt{5}}{2}x+(-\sqrt{5}x))}x\\ \large=\frac{1}{-\sqrt{5}}(\frac{1}{1-\frac{1-\sqrt{5}}{2}x}-\frac{1}{1-\frac{1+\sqrt{5}}{2}x})\\ \large=-\frac{1}{\sqrt{5}}\frac{1}{1-\frac{1-\sqrt{5}}{2}x}+\frac{1}{\sqrt{5}}\frac{1}{1-\frac{1+\sqrt{5}}{2}x} (1215 x)(121+5 x)x=(1215 x)((1215 x+(5 x))1x=5 1(1215 x1121+5 x1)=5 11215 x1+5 1121+5 x1

把他分裂成等比数列的形式。

a n = − 1 5 ( 1 − 5 2 ) n + 1 5 ( 1 + 5 2 ) n a_n=-\frac{1}{\sqrt{5}}(\frac{1-\sqrt{5}}{2})^n+\frac{1}{\sqrt{5}}(\frac{1+\sqrt{5}}{2})^n an=5 1(215 )n+5 1(21+5 )n

这就是斐波那契数列通项公式。

五边形数和欧拉函数和分割函数的那些事

hrhguanli的博客里面介绍清楚了五边形数和欧拉函数的关系,但看不了wiki百科的同学可能会疑惑分割函数是什么。分割函数主要是对于将一个正整数拆分成m个数这类问题所提出的名词,若不加限定, p ( n ) p(n) p(n)就代表将n拆分成其他数之和的方案个数。对于严格的分割函数定义,wiki做出了解释:

一个正整数可以写成一些正整数的和。在数论上,跟这些和式有关的问题称为整数拆分、整数剖分、整数分割、分割数或切割数(英语:Integer partition)。其中最常见的问题就是给定正整数 n n n,求不同数组
( a 1 , a 2 , . . . , a k ) {\displaystyle (a_{1},a_{2},...,a_{k})} (a1,a2,...,ak)的数目,符合下面的条件:
a 1 + a 2 + . . . + a k = n {\displaystyle a_{1}+a_{2}+...+a_{k}=n} a1+a2+...+ak=n(k的大小不定)
a 1 ≥ a 2 ≥ . . . ≥ a k > 0 {\displaystyle a_{1}\geq a_{2}\geq ...\geq a_{k}>0} a1a2...ak>0
其他附加条件(例如限定“ k k k是偶数”,或“ a i a_{i} ai不是 1 1 1就是 2 2 2”等)
分割函数 p ( n ) p(n) p(n)是求符合以上第一、二个条件的数组数目。

那么看了那个博客,应该就能知道五边形数在分割函数的应用了。直接上模板(n拆成其他数之和的方案):

//HDU 4651
//把数 n 拆成几个数(小于等于 n)相加的形式,问有多少种拆法。
#include
using namespace std;
const int MOD = 1e9+7;
int dp[100010];
void init()
{
    memset(dp,0,sizeof(dp));
    dp[0] = 1;
    for(int i = 1; i <= 100000; i++)    ///将1到100000的切割函数用五边形数的递减方式求出
    {
        for(int j = 1, r = 1; i - (3 * j * j - j) / 2 >= 0; j++, r*= -1)
        {
            dp[i] += dp[i -(3 * j * j - j) / 2] * r;///r来判别分割函数的正负
            dp[i] %= MOD;///(3 * j * j ± j) / 2是同正同负(指切割函数)的广义五边形数
            dp[i] = (dp[i]+MOD)%MOD;
            if( i - (3 * j * j + j) / 2 >= 0 )///先负后正,单调递增
            {
                dp[i] += dp[i - (3 * j * j + j) / 2] * r;
                dp[i] %= MOD;
                dp[i] = (dp[i]+MOD)%MOD;
            }
        }
    }
}
int main()
{
    int T;
    int n;
    init();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        printf("%d\n",dp[n]);
    }
    return 0;
}

限制:拆出来的数个数小于等于k:
证明:poursoul的博客——【HDU】4658 Integer Partition【生成函数——数拆分】
代码:

//数 n(<=10^5) 的划分,相同的数重复不能超过 k 个。
#include
using namespace std;
const int MOD = 1e9+7;
int dp[100010];
void init()
{
    memset(dp,0,sizeof(dp));
    dp[0] = 1;
    for(int i = 1; i <= 100000; i++)
    {
        for(int j = 1, r = 1; i - (3 * j * j - j) / 2 >= 0; j++, r*= -1)
        {
            dp[i] += dp[i -(3 * j * j - j) / 2] * r;
            dp[i] %= MOD;
            dp[i] = (dp[i]+MOD)%MOD;
            if( i - (3 * j * j + j) / 2 >= 0 )
            {
                dp[i] += dp[i - (3 * j * j + j) / 2] * r;
                dp[i] %= MOD;
                dp[i] = (dp[i]+MOD)%MOD;
            }
        }
    }
}
int solve(int n,int k)
{
    int ans = dp[n];
    for(int j = 1, r = -1; n - k*(3 * j * j - j) / 2 >= 0; j++, r*= -1)
    {
        ans += dp[n -k*(3 * j * j - j) / 2] * r;
        ans %= MOD;
        ans = (ans+MOD)%MOD;
        if( n - k*(3 * j * j + j) / 2 >= 0 )
        {
            ans += dp[n - k*(3 * j * j + j) / 2] * r;
            ans %= MOD;
            ans = (ans+MOD)%MOD;
        }
    }
    return ans;
}
int main()
{
    init();
    int T;
    int n,k;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&k);
        printf("%d\n",solve(n,k));
    }
    return 0;
}

求 A^B 的约数之和对 MOD 取模(唯一分解定理和)

思路(没研究透):kuangbin的博客

//参考 POJ 1845
//里面有一种求1+p+p^2+p…^3+p^n的方法。
//需要素数筛选和合数分解的程序,需要先调用 getPrime(); 
#include
using namespace std;

#define MOD 9901

//******************************************
//素数筛选和合数分解
const int MAXN=10000;
int prime[MAXN+1];
void getPrime()
{
    memset(prime,0,sizeof(prime));
    for(int i=2;i<=MAXN;i++)
    {
        if(!prime[i])prime[++prime[0]]=i;
        for(int j=1;j<=prime[0]&&prime[j]<=MAXN/i;j++)
        {
            prime[prime[j]*i]=1;
            if(i%prime[j]==0) break;
        }
    }
}
long long factor[100][2];
int fatCnt;
int getFactors(long long x)
{
    fatCnt=0;
    long long tmp=x;
    for(int i=1;prime[i]<=tmp/prime[i];i++)
    {
        factor[fatCnt][1]=0;
        if(tmp%prime[i]==0)
        {
            factor[fatCnt][0]=prime[i];
            while(tmp%prime[i]==0)
            {
                factor[fatCnt][1]++;
                tmp/=prime[i];
            }
            fatCnt++;
        }
    }
    if(tmp!=1)
    {
        factor[fatCnt][0]=tmp;
        factor[fatCnt++][1]=1;
    }
    return fatCnt;
}

//******************************************
long long pow_m(long long a,long long n)//快速模幂运算
{
    long long res=1;
    long long tmp=a%MOD;
    while(n)
    {
        if(n&1){res*=tmp;res%=MOD;}
        n>>=1;
        tmp*=tmp;
        tmp%=MOD;
    }
    return res;
}
long long sum(long long p,long long n)//计算1+p+p^2+````+p^n
{
    if(p==0)return 0;
    if(n==0)return 1;
    if(n&1)//奇数
    {
        return ((1+pow_m(p,n/2+1))%MOD*sum(p,n/2)%MOD)%MOD;
    }
    else return ((1+pow_m(p,n/2+1))%MOD*sum(p,n/2-1)+pow_m(p,n/2)%MOD)%MOD;

}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int A,B;
    getPrime();
    while(scanf("%d%d",&A,&B)!=EOF)
    {
        getFactors(A);
        long long ans=1;
        for(int i=0;i<fatCnt;i++)
        {
            ans*=(sum(factor[i][0],B*factor[i][1])%MOD);
            ans%=MOD;
        }
        printf("%I64d\n",ans);
    }
    return 0;
}

莫比乌斯反演

我又来转载别人的博客啦。因为自己现学缘故,所以没了解透。
别人的博客:大番茄番茄茄的博客
贴一道题:
B Z O J 2301 BZOJ2301 BZOJ2301
对于给出的 n 个询问,每次求有多少个数对 ( x , y ) (x, y) (x,y),满足 a < = x < = b , c < = y < = d a <= x <= b, c <= y <= d a<=x<=b,c<=y<=d,且 g c d ( x , y ) = k gcd(x, y) = k gcd(x,y)=k g c d ( x , y ) gcd(x, y) gcd(x,y) 函数为 x x x y y y 的最大公约数。 1 < = n < = 50000 , 1 < = a < = b < = 50000 , 1 < = c < = d < = 50000 , 1 < = k < = 50000 1 <= n <= 50000, 1 <= a <= b <= 50000, 1 <= c <= d <= 50000, 1 <= k <= 50000 1<=n<=50000,1<=a<=b<=50000,1<=c<=d<=50000,1<=k<=50000
代码:

#include
using namespace std;
const int MAXN = 100000;
bool check[MAXN+10];
int prime[MAXN+10];
int mu[MAXN+10];
void Mobius()
{
    memset(check,false,sizeof(check));
    mu[1] = 1;
    int tot = 0;
    for(int i = 2; i <= MAXN; i++)
    {
        if( !check[i] )
        {
            prime[tot++] = i;
            mu[i] = -1;
        }
        for(int j = 0; j < tot; j ++)
        {
            if( i * prime[j] > MAXN)
                break;
            check[i * prime[j]] = true;
            if( i % prime[j] == 0)
            {
                mu[i * prime[j]] = 0;
                break;
            }
            else
            {
                mu[i * prime[j]] = -mu[i];
            }
        }
    }
}
int sum[MAXN+10];
//找 [1,n],[1,m] 内互质的数的对数
long long solve(int n,int m)
{
    long long ans = 0;
    if(n > m)
        swap(n,m);
    for(int i = 1, la = 0; i <= n; i = la+1)
    {
        la = min(n/(n/i),m/(m/i));
        ans += (long long)(sum[la] - sum[i-1])*(n/i)*(m/i);
    }
    return ans;
}
int main()
{
    Mobius();
    sum[0] = 0;
    for(int i = 1; i <= MAXN; i++)
        sum[i] = sum[i-1] + mu[i];
    int a,b,c,d,k;
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
        long long ans = solve(b/k,d/k) - solve((a-1)/k,d/k) - solve(b/k,(c-1)/k) + solve((a-1)/k,(c-1)/k);
        printf("%lld\n",ans);
    }
    return 0;
}

Baby-Step Giant-Step

没看懂。求 a x = b ( m o d n ) a^x = b (mod n) ax=b(modn) x x x [ 0 , n ) [0,n) [0,n)的解 x x x
上道题:
POJ 2417,3243

#include
#include
#include
using namespace std;
#define MOD 76543
int hs[MOD],head[MOD],next[MOD],id[MOD],top;
void insert(int x,int y)
{
    int k = x%MOD;
    hs[top] = x, id[top] = y, next[top] = head[k], head[k] = top++;
}
int find(int x)
{
    int k = x%MOD;
    for(int i = head[k]; i != -1; i = next[i])
        if(hs[i] == x)
            return id[i];
    return -1;
}
int BSGS(int a,int b,int n)
{
    memset(head,-1,sizeof(head));
    top = 1;
    if(b == 1)
        return 0;
    int m = sqrt(n*1.0), j;
    long long x = 1, p = 1;
    for(int i = 0; i < m; ++i, p = p*a%n)
        insert(p*b%n,i);
    for(long long i = m; ; i += m)
    {
        if( (j = find(x = x*p%n)) != -1 )
            return i-j;
        if(i > n)
            break;
    }
    return -1;
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int a,b,n;
    while(scanf("%d%d%d",&n,&a,&b) == 3)
    {
        int ans = BSGS(a,b,n);
        if(ans == -1)
            printf("no solution\n");
        else
            printf("%d\n",ans);
    }
    return 0;
}

自适应Simpson积分(保证精度的积分算法)

介绍:xyz32768的博客
代码模板:

double simpson(double a,double b)
{
    double c = (a + b) / 2;
    return (F(a) + 4*F(c) + F(b))*(b-a)/6;//F()是函数,返回函数值
}
double asr(double a,double b,double eps,double A)
{
    double c = (a + b) / 2;
    double L = simpson(a,c), R = simpson(c,b);
    if(fabs(L + R - A) <= 15*eps)
        return L + R + (L + R - A)/15.0;
    return asr(a,c,eps/2,L) + asr(c,b,eps/2,R);
}
double asr(double a,double b,double eps)
{
    return asr(a,b,eps,simpson(a,b));
}

斐波那契数列取模循环节

必要时要上 unsigned long long
HDU3977
mezhuangzhuang的博客带你了解二次剩余。
代码:

#include
using namespace std;
long long gcd(long long a,long long b)
{
    if(b == 0)
        return a;
    return gcd(b,a%b);
}
long long lcm(long long a,long long b)
{
    return a/gcd(a,b)*b;
}
struct Matrix
{
    long long mat[2][2];
};
Matrix mul_M(Matrix a,Matrix b,long long mod)
{
    Matrix ret;
    for(int i = 0; i < 2; i++)
        for(int j = 0; j < 2; j++)
        {
            ret.mat[i][j] = 0;
            for(int k = 0; k < 2; k++)
            {
                ret.mat[i][j] += a.mat[i][k]*b.mat[k][j]%mod;
                if(ret.mat[i][j] >= mod)
                    ret.mat[i][j] -= mod;
            }
        }
    return ret;
}
Matrix pow_M(Matrix a,long long n,long long mod)
{
    Matrix ret;
    memset(ret.mat,0,sizeof(ret.mat));
    for(int i = 0; i < 2; i++)
        ret.mat[i][i] = 1;
    Matrix tmp = a;
    while(n)
    {
        if(n&1)
            ret = mul_M(ret,tmp,mod);
        tmp = mul_M(tmp,tmp,mod);
        n >>= 1;
    }
    return ret;
}
long long pow_m(long long a,long long n,long long mod)//a^b % mod
{
    long long ret = 1;
    long long tmp = a%mod;
    while(n)
    {
        if(n&1)
            ret = ret*tmp%mod;
        tmp = tmp*tmp%mod;
        n >>= 1;
    }
    return ret;
}
//素数筛选和合数分解
const int MAXN = 1000000;
int prime[MAXN+1];
void getPrime()
{
    memset(prime,0,sizeof(prime));
    for(int i = 2; i <= MAXN; i++)
    {
        if(!prime[i])
            prime[++prime[0]] = i;
        for(int j = 1; j <= prime[0] && prime[j] <= MAXN/i; j++)
        {
            prime[prime[j]*i] = 1;
            if(i%prime[j] == 0)
                break;
        }
    }
}
long long factor[100][2];
int fatCnt;
int getFactors(long long x)
{
    fatCnt = 0;
    long long tmp = x;
    for(int i = 1; prime[i] <= tmp/prime[i]; i++)
    {
        factor[fatCnt][1] = 0;
        if(tmp%prime[i] == 0)
        {
            factor[fatCnt][0] = prime[i];
            while(tmp%prime[i] == 0)
            {
                factor[fatCnt][1]++;
                tmp /= prime[i];
            }
            fatCnt++;
        }
    }
    if(tmp != 1)
    {
        factor[fatCnt][0] = tmp;
        factor[fatCnt++][1] = 1;
    }
    return fatCnt;
}
//勒让德符号
int legendre(long long a,long long p)
{
    if(pow_m(a,(p-1)>>1,p) == 1)
        return 1;
    else
        return -1;
}
int f0 = 1;
int f1 = 1;
long long getFib(long long n,long long mod)
{
    if(mod == 1)
        return 0;
    Matrix A;
    A.mat[0][0] = 0;
    A.mat[1][0] = 1;
    A.mat[0][1] = 1;
    A.mat[1][1] = 1;
    Matrix B = pow_M(A,n,mod);
    long long ret = f0*B.mat[0][0] + f1*B.mat[1][0];
    return ret%mod;
}
long long fac[1000000];
long long G(long long p)
{
    long long num;
    if(legendre(5,p) == 1)
        num = p-1;
    else
        num = 2*(p+1);
//找出 num 的所有约数
    int cnt = 0;
    for(long long i = 1; i*i <= num; i++)
        if(num%i == 0)
        {
            fac[cnt++] = i;
            if(i*i != num)
                fac[cnt++] = num/i;
        }
    sort(fac,fac+cnt);
    long long ans;
    for(int i = 0; i < cnt; i++)
    {
        if(getFib(fac[i],p) == f0 && getFib(fac[i]+1,p) == f1)
        {
            ans = fac[i];
            break;
        }
    }
    return ans;
}
long long find_loop(long long n)
{
    getFactors(n);
    long long ans = 1;
    for(int i = 0; i < fatCnt; i++)
    {
        long long record = 1;
        if(factor[i][0] == 2)
            record = 3;
        else if(factor[i][0] == 3)
            record = 8;
        else if(factor[i][0] == 5)
            record = 20;
        else
            record = G(factor[i][0]);
        for(int j = 1; j < factor[i][1]; j++)
            record *= factor[i][0];
        ans = lcm(ans,record);
    }
    return ans;
}
void init()
{
    getPrime();
}
int main()
{
    init();
    int T;
    int iCase = 0;
    int n;
    scanf("%d",&T);
    while(T--)
    {
        iCase++;
        scanf("%d",&n);
        printf("Case #%d: %I64d\n",iCase,find_loop(n));
    }
    return 0;
}

其它公式

Polya

我没整理,很大几率用不上bababababa…

你可能感兴趣的:(数学)