数论
快速幂
左移求幂次是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%i0) 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=resj%p;
res=res*qmi( i, p-2);
}
return res;
}