加载中...

数论

快速幂

左移求幂次是o1的 但最高支持63次方
快速幂是logn 的但最高支持10^9次方
取模要在乘法的时候取模

用来求a^k mod p 其中a k ,p 可以到1e9 可以左到o log k 而朴素 o k
计算出 2^1 2^2 组合变成k 本质将k变成二进制数
res =(连乘符号i到k的位数) a(2i)
(a*b) % k = ((a % k) * (b % k)) % k;

int qmi(int a,int k ,int p){
  int res=1;
  while(k){//拆开k
     if(k%i)//二进制下末尾是1的话 就乘不是就不乘
      res=(LL)res*a%p;
      k>>=1;//删掉k的二进制下的最后一位
      a=(LL)a*a%p;//a变成下一个平方
  }     

  return res;
}

欧拉降幂 当指数k非常大只能用字符串存的的时候

#include<bits/stdc++.h>
using namespace std;
#define int long long

int qmi(int a,int k,int p){
    int res=1;
    while(k){
        if(k&1) res=(res*a)%p; 
        a=a*a%p;
        k>>=1;
    }
    return res;
}

int eu(int n){//求欧拉函数
    int res=n;
    
    for(int i=2;i<n/i;i++){
        if(n%i==0 ) {
            res=(res/i*(i-1));    
            while(n%i==0) n/=i;
        
        }
        
    }
    if(n>1) 
    res=(res/n*(n-1));  
    return res;
}
signed main(){
    int t;cin>>t;
    while(t--){
        string y;
        int x,p;cin>>x>>y>>p;
        if(x==0&&y[0]=='0'){//特别判断
            cout<<1<<endl;
            continue;
        }
        int phi=eu(p);
        int k=0;
        for(int i=0;i<y.size();i++){
            k=((k*10)+y[i]-'0')%phi;//每次余欧拉函数
        }
        
        k+=phi;//记得加上
        
        cout<<qmi(x,k,p)<<endl;
        
    }
    return 0;
}




快速幂求逆元 qmi( a,p-2 ,p)

只适用于mod数为质数的情况下 而算法题 一般都满足整个
满足gcd(a,b)=1的意思是 a,b互余
求3的逆元在模p下就是找一个值 记作a^-1
使得 3*(a^-1)≡1 (在mod p的环境下)

快速幂矩阵 计算递推多项式

计算快速幂矩阵

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 3;

int n, m;

void mul(int c[], int a[], int b[][N])//一个方程相乘的情形
{
    int temp[N] = {0};
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            temp[i] = (temp[i] + (LL)a[j] * b[j][i]) % m;
//这里看开点 可能是 a[1][j]*b[j][i]
    memcpy(c, temp, sizeof temp);
}

void mul(int c[][N], int a[][N], int b[][N])//一个方程相乘的情形
{//c是答案 最后一步才用上 a 和 b 是要乘的矩阵
    int temp[N][N] = {0};//c是
    for (int i = 0; i < N; i ++ )//i是行
        for (int j = 0; j < N; j ++ )//j是列
            for (int k = 0; k < N; k ++ )
                temp[i][j] = (temp[i][j] + (LL)a[i][k] * b[k][j]) % m;
//利用了矩阵相乘的公式 三个for循环
    memcpy(c, temp, sizeof temp);
}

int main()
{
    cin >> n >> m;

    int f1[N] = {1, 1, 1};//初始答案矩阵(转置)
    int a[N][N] = {//快速幂矩阵
        {0, 1, 0},
        {1, 1, 1},
        {0, 0, 1}
    };

    n -- ;
    while (n)//类似于 a的(n-1) 答案用res=1开始乘 但是这里答案有a开始乘
    {
        if (n & 1) mul(f1, f1, a);  // res = res * a
        mul(a, a, a);  // a = a * a
        n >>= 1;
    }

    cout << f1[2] << endl;

    return 0;
}


垒骰子 dp+快速幂矩阵https://www.acwing.com/problem/content/1219/

f[i][j]为i个骰子最上面的数为j 的所有方案的总和
f[i+1][j]=f[i][1]+f[i][2]+f[i][3]+f[i][4]+f[i][5]+f[i][6]

f[i]=f[i-1]a;
f[n]=f[1]
A^(n-1);

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
const int N = 6,mod=1e9+7;
int n,m;
int get_op(int x){
    if(x>=3) return x-3;
    else return x+3;
}
void mul(int a[][N],int b[][N],int c[][N]){
    int t[N][N];
    memset(t, 0, sizeof t);
    for (int i = 0; i < N; i ++ ){
        for (int j = 0; j < N; j ++ ){
            for (int k = 0; k < N; k ++ ){
                t[i][j]=(t[i][j]+(b[i][k]*c[k][j])%mod)%mod;
            }
        }
    }
    memcpy(a , t,sizeof t);
    
}
signed main()
{
    cin>>n>>m;
    int a[N][N];
    
    for (int i = 0; i < N; i ++ ){
        for (int j = 0; j < N; j ++ ) a[i][j]=4;
    }
    
    while (m -- ){
        int x,y;cin>>x>>y;
        x--;y--;
        a[x][get_op(y)]=0;
        a[y][get_op(x)]=0;
        
    }
    int f[N][N]={4,4,4,4,4,4};
    for (int k = n-1; k;  k>>=1 ){
        if(k&1) mul(f,f,a);
        mul(a,a,a);
        
        
    }
    int res=0;
    for (int i = 0; i < N; i ++ ){
        res=(res+f[0][i])%mod;   
    }
    cout<<res;
    
    return 0;
}

对于逆元c,在数值上就不一定等于我们常规意义上的倒数了,我们可以理解为要求在0,1,2……p-1之间找一个数,是的这个数和a相乘后再取模p,得到的结果为1。

原理根据费马小定理
a^(p-1)≡1(mod p)
两边同时除a
a(p-2)≡a(-1) (mod p)
所以
a(-1)≡a(p-2) (mod p)

res=qmi(a,p-2,p)
int qmi(int a,int k ,int p){
  int res=1;
  while(k){//拆开k
     if(k%i)//二进制下末尾是1的话 就乘不是就不乘
      res=(LL)res*a%p;
      k>>=1;//删掉k的二进制下的最后一位
      a=(LL)a*a%p;//a变成下一个平方
  }     

  return res;
}

位运算

lowbit 用于获得一个数x的最低位1 如6=(110) lowbit(6)=(10)

实现:返回x和-x

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int lowbit(int x){
    return x&-x;//-x等于取反x+1  x和-x的1都在同一位置 取and即可
}
const int N = 1e6+10;
int x;
int main()
{
    int n;cin>>n;
    for (int i = 0; i < n; i ++ ) {
       cin >> x;
       int res=0;
       while(x){x-=lowbit(x),res++;//lowbit返回最低为的1,如果lowbit为0,说明以及是0,就不用减去了
           
       }
       cout<<res<<" ";

    

    return 0;
}

#质数
##判断是不是质数

bool divide(int n){
if(n<2) return false;
for (int i = 2; i <= n/i; i ++ ){
if(n%i==0) return false;
}
return true;
}

##

void divide(int a){
for (int i = 2; i <= a/i; i ++ ){
if(a%i0){
int s=0;
while(a%i
0) s++,a/=i;
cout << i<<" "<<s<<endl;
}
}
if(a>1)
cout << a<<" "<<1<<endl;
}



##求1-n中的质数个数
i循环都是2-n

void get_primes2(){
for(int i=2;i<=n;i++){

    if(!st[i]) primes[cnt++]=i;//把素数存起来 如果现在的还是否 说明是质数
    //先执行这里
    for(int j=i;j<=n;j+=i){//不管是合数还是质数,都用来筛掉后面它的倍数
        st[j]=true;//因为j+=i到n,st将每个i的倍数拿出都筛选出来
    }
}

}
埃氏筛法:可以用质数就把所有的合数都筛掉;
把上面的for循环放到if里面

void get_primes1(){
    for(int i=2;i<=n;i++){
        if(!st[i]){
            primes[cnt++]=i;
            for(int j=i;j<=n;j+=i) st[j]=true;//可以用质数就把所有的合数都筛掉;
        }
    }
}
线性筛
void get_primes(){
    //外层从2~n迭代,因为这毕竟算的是1~n中质数的个数,而不是某个数是不是质数的判定
    for(int i=2;i<=n;i++){
        if(!st[i]) primes[cnt++]=i;//为真的化就成立
//先进行下面的
//注意没有else
        for(int j=0;primes[j]<=n/i;j++){//primes[j]<=n/i:变形一下得到——primes[j]*i<=n,把大于n的合数都筛了就
        //没啥意义了
            st[primes[j]*i]=true;//用最小质因子去筛合数 每个n只用最小的质因子去筛掉(通过j++ 即每个质数增加来确定)
//primes[j]是primes[j]*i的最小公因子,但不是i的最小公因子

            if(i%primes[j]==0) break;//周到最小的质因子
//从小到大枚举质数 退出循环,保证只筛一次。pj是i最小质因子 ;不退出以后就会用到i的第二小质因子去筛选 但是这样i++后的下一轮循环j还是从prime[0]开始就可以
//primes[j]是primes[j]*i的最小公因子,也是i的最小公因子
        }
    }

}


分解质因数

性质1:n中最多只含有一个大于sqrt(n)的质因子。
性质2:完全平方数必然由偶数次的质因数构成 如22 33 44=2222 55 66=232*3

#include<bits/stdc++.h>
using namespace std;

void divide(int n){
    for(int i=2;i<=n/i;i++){//小于等于号
        if(n%i==0){       //n不包含任何从2到i-1之间的质因子(已经被除干净了)
                          //(n%i==0)所以i也不包含何从2到i-1之间的质因子,由质数的定义可知,保证了i是质数
            int s=0;
            while(n%i==0) n/=i,s++;//两个判断都是整除  
            cout<<i<<' '<<s<<endl;
        }
    }  
    if(n>1) cout<<n<<' '<<1<<endl;    //最多只有一个大于根下n的质因子(两个相乘就大于n了)
    cout<<endl;
}




完全平方数 答案就是奇数次的质因数

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
long long  n;
void divide(long long  a){
    long long  res=1;
    for (long long  i = 2; i <= a/i; i ++ ){
        if(a%i==0){
            long long  s=0;
            while(a%i==0) s++,a/=i ;
            // cout << i<<" "<<s <<endl;
            if(s%2!=0) res*=i;
        }
        
    }
    
    if(a>1) res*=a;
    cout << res;
}
int main()
{
    cin >> n;
    divide(n);
    return 0;
}

求所有约数


vector<int>  divide(int n){
    vector<int >s;
    for (int i = 1; i <= n/i; i ++ ){
        if(n%i==0 ){
            s.push_back(i);
            if(i!=n/i){
                s.push_back(n/i);
            }
        }   
    }
    sort(s.begin(),s.end());
    return s;
}

约数个数

p为质数
N = (p1^x1)(p2^x2)(p3^x3)…(pk^xk)
约数个数=(x1+1)(x2+1)(x3+1)…(xk+1)

因为每一种pi都有0->xi种选法,一共xi+1种,一共k个所以迭代k次

这么讲不够直白,接下来举个栗子
24=2*2*2*3=2³*3
再用各个质数的指数加一后再相乘即为此数的约数个数,
比如 (3+1)*(1+1)=4*2=8, 即表示24有8个约数。
24的约数:1、2、3、4、6、8、12、24
所以关键就是 得到构成n的所有质数的幂次
然后答案=n个(幂次+1)相乘 就是约数的个数
-------------
      unordered_map<int ,int >primes;//存储所有的底数和指数
    while(n--)
    {
        int x;
        cin>>x;

        for(int i=2;i<=x/i;i++)
            while(x%i==0)
            {
                x/=i;
                primes[i]++;//i的质因数指数+1
            }
            if(x>1)primes[x]++;
    }

    //以上过程完成primes就存了所有质因数的指数

    LL res=1;
    for(auto prime:primes) res = res*(prime.second+1)%mod;

    cout<<res<<endl;


约数之和
这里的通过提取公因式得到的
(p10+p11+…+p1c1)∗…∗(pk0+pk1+…+pkck)
而括号内的指数求和
int t = 1;
while(a --)
t = t * p + 1;
秦九韶算法

 unordered_map<int, int> primes;

    while (n -- )
    {
        int x;
        cin >> x;

        for (int i = 2; i <= x / i; i ++ )
            while (x % i == 0)
            {
                x /= i;
                primes[i] ++ ;
            }

        if (x > 1) primes[x] ++ ;
    }

    LL res = 1;
    for (auto p : primes)
    {
        LL a = p.first, b = p.second;
        LL t = 1;
        while (b -- ) t = (t * a + 1) % mod;
        res = res * t % mod;
    }

    cout << res << endl;




欧拉函数

int phi(int x)
{
    int res = x;
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            res = res / i * (i - 1);
            while (x % i == 0) x /= i;
        }
    if (x > 1) res = res / x * (x - 1);

    return res;
}

求1-n欧拉函数之和

线性筛法中顺便把欧拉函数求了

void get_eulers(int n)
{
    phi[1] = 1;//****
    for (int i = 2; i <= n; i++)
    {
        if (!st[i])
        {
            primes[cnt++] = i;
            phi[i] = i - 1; //****
//对于质数i 1~i-1都与他互质
        }

        for (int j = 0; primes[j] <= n / i; j++)
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0)
            {
                phi[primes[j] * i] = phi[i] * primes[j]; //****
//
                break;
            }
            phi[primes[j] * i] = phi[i] * (primes[j] - 1);///***这里pj本身是个素数
//pj是pj*i的最小质因子 所以要将pj*i比i多了一个质数pj 所以由欧拉函数 需要多算一个(1-1/pj)
//如果i和p互质 那么 phi[i*p]=phi[i]*phi[p]
        }
    }


exgcd

设ax1+by1=gcd(a,b), bx2+(a%b)y2=gcd(b,a%b);
由gcd(a,b)=gcd(b,a%b),可得:
ax1+by1=bx2+(a%b)y2;
即:ax1+by1=bx2+(a-(a/b)b)y2
=ay2+bx2-(a/b)
by2;
即:ax1+by1=ay2 + b(x2-(a/b)y2)
根据恒等定理,对应项相等,得:x1=y2; y1=x2-(a/b)
y2;
这样我们就得到了:x1,y1的值基于x2,y2,所以我们可以通过递归求解。

写法1:
void Exgcd(int a, int b, int &x, int &y) {
    if (!b)
        x = 1, y = 0;
    else {
        Exgcd(b, a % b, x, y);
        int t = x;
        x = y;
        y = t - a / b * y;
    }
}

写法2:
int exgcd(int a, int b, int &x, int &y){//返回gcd(a,b) 并求出解(引用带回)
    
    if(!b){
        x=1,y=0;
        return a;//此时最大公约数是a 因为1*a+0*b == a(gcd);
    }
    int x1,y1,gcd;
    gcd=exgcd(b,a%b,y,x);(这里将y和x换了位置 所以少用一个变量)
    y-= (a/b)*x;
    return gcd;
    

}
##求线性同余方程 求x满足 a*x=b(mod m)
因为 a∗x≡b(mod m)a∗x≡b(mod m) 等价于 a∗x−ba∗x−b 是m的倍数,因此线性同余方程等价为 a∗x+m∗y=ba∗x+m∗y=b
根据 Bezout 定理,上述等式有解当且仅当 gcd(a,m)|bgcd(a,m)|b
因此先用扩展欧几里得算法求出一组整数 x0,y0x0,y0, 使得 a∗x0+m∗y0=gcd(a,m)a∗x0+m∗y0=gcd(a,m)。 然后 x=x0∗b/gcd(a,m)%mx=x0∗b/gcd(a,m)%m 即是所求。
b是gcd 的倍数

include

include

include

using namespace std;
int exgcd(int a,int b,int &x,int &y){
if(!b){
x=1,y=0;
return a;

}
int d=exgcd( b,a%b ,y ,x );
y-=(a/b) *x;

return d;

}

int main()
{
int n;
cin >> n;
while(n--){
int a,b,m;
cin >> a>>b>>m;
int x,y;
int d=exgcd( a, m,x,y);
if(b%d) cout<<"impossible"<<endl;
else cout << (long long )xb/d%m <<endl;//记得mod上m xb/d;
}
return 0;
}


组合数学
1.0<a<b<=1e5
使用阶乘公式 预处理fac 和 infac 

fac[0]=infac[0]=1;
for(int i=1;i<=n;i++){
fac[i]=fac[i-1]i;
infac[i]=infac[i-1]
qmi( i, p-2 ,p);
}
cout<<(fac[n]infac[k]infac[n-k];

2. 0<a<b<=1e18 p=1e5 卢卡斯

int lucas(int a,int b){
if(a<p&&b<p) return c( a,b);
return (ll)c(a%p,b%p)lucas(a/p,b/p)%p;
}
int c(int a,int b){//普通求
int res=1;
for(int i=1,j=n; i<=b; i++){
res=res
j%p;
res=res*qmi( i, p-2);

}
return res;

}

posted @ 2022-03-01 22:02  liang302  阅读(114)  评论(0编辑  收藏  举报