数论
数论
一、质数
1)试除法判断质数
bool isPrime(int x)
{
if(x < 2) return false;
for(int i = 2;i<=x/i;i++)
if(x%i==0) return false;
return true;
}
2)分解质因数
void divide(int n)
{
for (int i = 2; i <= n / i; i ++ )
{
if( n % i == 0 )
{
int s = 0;
while (n % i == 0)
{
n /= i;
s++;
}
printf("%d %d\n",i,s);
}
}
if (n > 1) printf("%d %d\n",n,1);
puts("");
}
3)筛质数
1、普通筛质数
void getPrimes(int n)
{
for (int i = 2; i <= n; i ++)
{
if (!st[i])
{
primes[cnt ++] = i;
}
for (int j = i + i; j <= n; j ++) st[j] = true;
}
}
2、埃氏筛质数
void getPrimes(int n)
{
for (int i = 2; i <= n; i ++)
{
if (!st[i])
{
primes[cnt ++] = i;
for (int j = i + i; j <= n; j ++) st[j] = true; //只对质数的倍数筛掉
}
}
}
3、欧拉筛
void getPrimes(int n)
{
for (int i = 2; i <= n; i ++)
{
if(!st[i]) primes[cnt ++] = i;
for (int j = 0; primes[j] <= n / i; j ++)
{
st[primes[j] * i] = true; //每次把对应的倍数筛掉
if (i % primes[j] == 0) break; //primes[j]是i的最小质因子,我们只用最小质因子来筛,所以遇到了就break
}
}
}
二、约数
1)试除法求约数
#include <bits/stdc++.h>
using namespace std;
int n;
vector<int> gcd(int n)
{
vector<int> res;
for(int i = 1; i <= n / i; i ++)
{
if(n % i == 0)
{
res.push_back(i);
if (i != n / i) res.push_back(n / i);
}
}
sort(res.begin(),res.end());
return res;
}
int main()
{
scanf("%d", &n);
while (n --)
{
int x;
scanf("%d", &x);
auto res = gcd(x);
for(auto item:res) printf("%d ", item);
puts("");
}
return 0;
}
2)求n个数的积对常数取模的结果
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e9+7;
int n;
int main()
{
cin >> n;
ll res = 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] ++ ;
}
//至此,我们就把最后的乘积的每个质因子的指数都求出来了,根据公式得出约数个数是(a1+1)*(a2+1)……*(ak+1);
for (auto item : primes) res = res * (item.second + 1) % N;
printf("%lld", res);
return 0;
}
3)求n个数的积的约数个数
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int mod = 1e9 +7;
int n;
int main()
{
cin>>n;
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 prime:primes)
{
int p = prime.first, a = prime.second;
ll t = 1;
while (a -- ) t = (t * p + 1) % mod;
res = res * t % mod;
}
cout<<res;
return 0;
}
4)求最大公约数
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
三、欧拉函数
欧拉函数的证明
1)欧拉函数
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int n;
cin >> n;
while (n --)
{
int a;
cin >> a;
int res = a; // res存储最终的结果
for (int i = 2; i <= a / i; i ++) // 对数据先进行质因数分解
{
if(a % i == 0) // i是a的一个约数
{
res = res / i * (i - 1); // 化简之后的,相当于乘以 1-(1/i)
while (a % i == 0) a /= i;
}
}
if(a > 1) res = res / a * (a - 1);
cout << res <<endl;
}
return 0;
}
2)筛法求欧拉函数
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e6+5;
int primes[N],cnt;
ll phi[N]; // 欧拉函数
bool st[N]; // 标记是否是质数
ll get_Olers(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;
}
//如果primes[j]不是i的质因子,那么phi[primes[j]*i] = phi[i] * primes[j] * (1-1/primes[j]),化简得到如下代码
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
ll res = 0;
for(int i = 1; i <= n; i ++) res += phi[i];
return res;
}
int main()
{
int n;
cin >> n;
cout << get_Olers(n) << endl;
return 0;
}
四、快速幂
欧拉定理
快速幂
#include <iostream>
using namespace std;
typedef long long ll;
ll qmi(ll a, ll k, ll p)
{
ll res = 1;
while (k)
{
if (k & 1) res = res * a % p;
k = k >> 1;
a = a * a % p;
}
return res;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
ll a,k,p;
scanf("%lld%lld%lld",&a,&k,&p);
printf("%lld\n",qmi(a,k,p));
}
return 0;
}
快速幂求逆元
#include <iostream>
using namespace std;
typedef long long ll;
ll qmi(ll a, ll k, ll p)
{
ll res = 1;
while (k)
{
if (k & 1) res = res * a % p;
k = k >> 1;
a = a * a % p;
}
return res;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
ll a,p;
scanf("%lld%lld",&a,&p);
ll res = qmi(a,p-2,p);
if (a % p) printf("%lld\n",res);
else puts("impossible");
}
return 0;
}
五、扩展欧几里得算法
裴属定理
扩展欧几里得算法(求ax + by = gcd(a,b) 中的x和y的算法)
#include <iostream>
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,x,y;
scanf("%d%d",&a, &b);
exgcd(a,b,x,y);
printf("%d %d\n",x,y);
}
return 0;
}
线性同余方程(求 ax % m = b 式子中的x,给定a,b,m)
#include <iostream>
using namespace std;
typedef long long ll;
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;
scanf("%d%d%d",&a,&b,&m);
int x,y;
int d = exgcd(a,m,x,y);
if (b % d) puts("impossible");
else printf("%d\n",(ll)x * b / d % m);
}
return 0;
}
六、中国剩余定理
题目:
给定 2n2𝑛 个整数 a1,a2,…,an𝑎1,𝑎2,…,𝑎𝑛 和 m1,m2,…,mn𝑚1,𝑚2,…,𝑚𝑛,求一个最小的非负整数 x𝑥,满足 ∀i∈[1,n],x≡mi(mod ai)∀𝑖∈[1,𝑛],𝑥≡𝑚𝑖(𝑚𝑜𝑑 𝑎𝑖)。
输入格式
第 11 行包含整数 n𝑛。
第 2…n+12…𝑛+1 行:每 i+1𝑖+1 行包含两个整数 ai𝑎𝑖 和 mi𝑚𝑖,数之间用空格隔开。
输出格式
输出最小非负整数 x𝑥,如果 x𝑥 不存在,则输出 −1−1。
数据范围
1≤ai≤231−11≤𝑎𝑖≤231−1,
0≤mi<ai0≤𝑚𝑖<𝑎𝑖
1≤n≤251≤𝑛≤25
所有 mi𝑚𝑖 的最小公倍数在 6464 位有符号整数范围内。输入样例:
2 8 7 11 9
输出样例:
31
#include<iostream>
using namespace std;
typedef long long LL;//数据范围比较大,所以用LL来存储
LL exgcd(LL a,LL b,LL &x,LL &y)
{
if(!b)
{
x=1,y=0;
return a;
}
LL d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}
int main()
{
int n;
LL a1,m1;
cin>>n>>a1>>m1;
LL x=0;
for(int i=1;i<n;i++)
{
LL a2,m2;
cin>>a2>>m2;
LL k1,k2;
LL d=exgcd(a1,a2,k1,k2);
if((m2-m1)%d)
{
x=-1;
break;
}
k1*=(m2-m1)/d;
//因为此时k1是k1*a1+k2*a2=d的解,所以要乘上(m2-m1)/d的倍数大小
LL t=abs(a2/d);
k1=(k1%t+t)%t;
//数据比较极端,所以只求k的最小正整数解
m1=k1*a1+m1;
//m1在被赋值之后的值为当前"x"的值,此时赋值是为了方便下一轮的继续使用
a1=abs(a1*a2/d);
//循环结束时a1的值为当前所有的a1,a2,……an中的最小公倍数
}
if(x!=-1)
x=(m1%a1+a1)%a1;
//当循环结束时,此时的值应该与最小公倍数取模,以求得最小正整数解
printf("%lld\n",x);
return 0;
}
七、高斯消元法解方程
1)高斯消元法解线性方程组
#include <iostream>
#include <cmath>
using namespace std;
const int N = 110;
const double eps = 1e-6;
int n;
double a[N][N];
int gauss() // 高斯消元,答案存于a[i][n]中,0 <= i < n
{
int c, r;
for (c = 0, r = 0; c < n; c ++ )
{
int t = r;
for (int i = r; i < n; i ++ ) // 找绝对值最大的行
if (fabs(a[i][c]) > fabs(a[t][c]))
t = i;
if (fabs(a[t][c]) < eps) continue;
for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端
for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将当前行的首位变成1
for (int i = r + 1; i < n; i ++ ) // 用当前行将下面所有的列消成0
if (fabs(a[i][c]) > eps)
for (int j = n; j >= c; j -- )
a[i][j] -= a[r][j] * a[i][c];
r ++ ;
}
if (r < n)
{
for (int i = r; i < n; i ++ )
if (fabs(a[i][n]) > eps)
return 2; // 无解
return 1; // 有无穷多组解
}
for (int i = n - 1; i >= 0; i -- )
for (int j = i + 1; j < n; j ++ )
a[i][n] -= a[i][j] * a[j][n];
return 0; // 有唯一解
}
int main()
{
cin >> n;
for(int i = 0; i < n; i ++ )
for(int j = 0; j < n+1; j ++ )
cin >> a[i][j];
int t = gauss();
if (!t)
{
for (int i = 0; i < n; i ++ ) printf("%.2lf\n", a[i][n]);
}else if (t == 1) printf("Infinite group solutions");
else printf("No solution");
return 0;
}
2)高斯消元解异或线性方程组
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n;
int a[N][N];
int gauss()
{
int c,r;
for(c=0,r=0;c<n;c++)
{
// 找主元
int t=-1;
for(int i=r;i<n;i++)
if(a[i][c])
{
t=i;
break;
}
if(t==-1) continue;
// 交换主元行
for(int j=c;j<=n;j++) swap(a[r][j],a[t][j]);
// 左下角消
for(int i=r+1;i<n;i++)
if(a[i][c])//漏了
for(int j=n;j>=c;j--)//漏了
a[i][j] ^= a[r][j];
r++;
}
// 判断
if(r<n)
{
for(int i=r;i<n;i++)//i=r
if(a[i][n])
return 2;
return 1;
}
// 消右上角
for(int i=n-1;i>=0;i--)
for(int j=i+1;j<n;j++)
//如果是0 就不用下面的a[j][j] 来^a[i][j]了
//如果不是0 才需要用第j行第j列a[j][j]来^第i行第j列a[i][j]
//进而进行整行row[i]^row[j] 间接导致 a[i][n]^a[j][n]
if(a[i][j])
a[i][n]^=a[j][n];
return 0;
}
int main()
{
cin >> n;
for(int i=0;i<n;i++)
for(int j=0;j<=n;j++)
cin >> a[i][j];
int t = gauss();
if(t==0)
{
for(int i=0;i<n;i++) cout << a[i][n] << endl;
}
else if(t==1) puts("Multiple sets of solutions");
else puts("No solution");
return 0;
}
八、求组合数
1)求组合数Ⅰ
#include <iostream>
using namespace std;
const int N = 2001, mod = 1e9+7;
int c[N][N];
void init()
{
for(int i = 0; i < N; i ++ )
for(int j = 0; j <= i; j ++ )
if (!j) c[i][j] = 1;
else c[i][j] = (c[i-1][j-1] + c[i-1][j]) % mod;
}
int main()
{
init();
int n;cin >> n;
while (n -- )
{
int a, b;
cin >> a >> b;
cout << c[a][b] <<endl;
}
return 0;
}
2)求组合数 II
当N的数量级超过几千的时候,就不能根据递推式一次性处理完了,用组合数的定义来处理)
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1000010, mod = 1e9+7;
ll fact[N], infact[N];
ll qmi(int a, int k , int p)
{
ll res = 1;
while (k)
{
if (k & 1) res = res * a % p;
k >>= 1;
a = (ll)a * a % p;
}
return res;
}
int main()
{
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ )
{
fact[i] = fact[i-1] * i % mod;
infact[i] = infact[i-1] * qmi(i, mod-2, mod) % mod; // qmi(i, mod - 2, mod) 是i模mod的逆元,即1/i (% mod)
}
int n;cin >> n;
while (n -- )
{
int a, b;cin >> a >> b;
cout << fact[a] * infact[b] % mod * infact[a-b] % mod << endl;
}
return 0;
}
3)求组合数Ⅲ
N的数量级非常大的时候,用卢卡斯定理: C[a][b] = C[a%p][b%p] * C[a/p][b/p]; p是对结果取模的数
#include <iostream>
using namespace std;
typedef long long ll;
int p;
int qmi(int a,int k) // p是全局变量,就不作形参了
{
int res = 1;
while (k)
{
if (k & 1) res = (ll) res * a % p;
a = (ll)a * a % p;
k >>= 1;
}
return res;
}
int C(int a, int b)
{
int res = 1;
for (int i = 1, j = a; i <= b; i ++, j -- )
{
res = (ll)res * j % p;
res = (ll)res * qmi(i, p-2) % p;
}
return res;
}
ll lucas(ll a, ll b)
{
if (a < p && b < p) return C(a, b) % p;
return (ll)C(a % p, b % p) * lucas(a / p, b / p) % p;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
ll a ,b;
cin >> a >> b >> p;
cout << lucas(a, b) << endl;
}
return 0;
}
4)求组合数Ⅳ
用高精度
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=5010;
int primes[N],cnt;
int sum[N];
bool st[N];
void get_primes(int n)
{
for(int i=2;i<=n;i++)
{
if(!st[i])primes[cnt++]=i;
for(int j=0;primes[j]*i<=n;j++)
{
st[primes[j]*i]=true;
if(i%primes[j]==0)break;//==0每次漏
}
}
}
// 对p的各个<=a的次数算整除下取整倍数
int get(int n,int p)
{
int res =0;
while(n)
{
res+=n/p;
n/=p;
}
return res;
}
//高精度乘
vector<int> mul(vector<int> a, int b)
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size(); i ++ )
{
t += a[i] * b;
c.push_back(t % 10);
t /= 10;
}
while (t)
{
c.push_back(t % 10);
t /= 10;
}
// while(C.size()>1 && C.back()==0) C.pop_back();//考虑b==0时才有pop多余的0 b!=0不需要这行
return c;
}
int main()
{
int a,b;
cin >> a >> b;
get_primes(a);
for(int i=0;i<cnt;i++)
{
int p = primes[i];
sum[i] = get(a,p)-get(a-b,p)-get(b,p);//是a-b不是b-a
}
vector<int> res;
res.push_back(1);
for (int i = 0; i < cnt; i ++ )
for (int j = 0; j < sum[i]; j ++ )//primes[i]的次数
res = mul(res, primes[i]);
for (int i = res.size() - 1; i >= 0; i -- ) printf("%d", res[i]);
puts("");
return 0;
}
5)卡特兰数
\(C_{2n}^{n} - C_{2n}^{n-1} = \frac{C_{2n}^{n}}{n+1}\)
提供了两个板子,但是第二个似乎更好用一些
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 200010, mod = 1e9+7;
ll fact[N], infact[N];
ll qmi(int a, int k , int p)
{
ll res = 1;
while (k)
{
if (k & 1) res = res * a % p;
k >>= 1;
a = (ll)a * a % p;
}
return res;
}
int main()
{
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ )
{
fact[i] = fact[i-1] * i % mod;
infact[i] = infact[i-1] * qmi(i, mod-2, mod) % mod; // qmi(i, mod - 2, mod) 是i模mod的逆元,即1/i (% mod)
}
int n;
cin >> n;
cout << fact[2*n] * infact[n] % mod * infact[n] % mod * qmi(n+1, mod -2, mod) % mod<< endl;
return 0;
}
#include <iostream>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
int qmi(int a, int k, int p)
{
ll res = 1;
while (k)
{
if (k & 1) res = (ll)res * a % p;
k >>= 1;
a = (ll)a * a %p;
}
return res;
}
int main()
{
int n;
cin >> n;
int a = 2 * n, b = n;
int res = 1;
for (int i = a; i > a-b; i --)
{
res = (ll)res * i % mod;
}
for (int j = 1; j <= b; j ++){
res = (ll)res * qmi(j, mod-2, mod) % mod;
}
cout << (ll)res * qmi(n+1, mod - 2, mod) % mod << endl;
return 0;
}
九、容斥原理
题目:
给定一个整数 n𝑛 和 m𝑚 个不同的质数 p1,p2,…,pm𝑝1,𝑝2,…,𝑝𝑚。
请你求出 1∼n1∼𝑛 中能被 p1,p2,…,pm𝑝1,𝑝2,…,𝑝𝑚 中的至少一个数整除的整数有多少个。
输入格式
第一行包含整数 n𝑛 和 m𝑚。
第二行包含 m𝑚 个质数。
输出格式
输出一个整数,表示满足条件的整数的个数。
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 20;
int p[N];
int main()
{
int n, m;
cin >> n >> m;
// 用p数组存储m个质数
for (int i = 0; i < m; i ++ ) cin >> p[i];
int res = 0;
// --------------------1、每一个i代表一种可能的取法,最外层的循环遍历置2的m次方后,可以取完所有的取法--------------------
// ① 为什么是2的m次方?最外层循环的作用是什么?
// 从1开始枚举,枚举到1 << m(左移m位。左移一位相当于乘2,右移一位相当于除2),即2的m次方;
for (int i = 1; i < 1 << m ; i ++ )
{
// t代表当前所有质数的乘积,s代表什么当前选法包含几个集合
int t = 1, s = 0;
// --------------------2、这个循环就是提取出这个i值对应的取法--------------------
// 枚举m个质数,依次计算容斥原理的公式
for (int j = 0; j < m; j ++ ){
// i右移j位与上1,即如果当前位是1的话
// ② i在这里为什么要用位运算?
if (i >> j & 1)
{
// (LL)t * p[j] > n
// 如果t(已有的质数选法)乘上这个质数大于给定的数n,说明1∼n中的数不能被p整除
// 此时直接返回break,跳过这个质数
if ((LL)t * p[j] > n)
{
t = -1;
break; // break的作用域是跳出整个循环
}
// 将该质数乘到t中
t *= p[j];
// s表示当前选法中有多少个集合
s ++ ;
}
}
// --------------------3、再将提取出的取法代入公式--------------------
// 如果t不等于-1(-1是给定的flag值)
if (t != -1)
{
// ③ s为什么要模2?
if (s % 2) res += n / t;
else res -= n / t;
}
}
cout << res << endl;
return 0;
}
十、博弈论
1、Nim游戏
先把所有的数异或得到一个结果:
1、结果为0:作为先手,那么不管取出多少物品,之后所有的数再次异或得到的结果就肯定不再为0
2、结果为x(x != 0,且x的最高位为1是第k位):那么在a1~an中肯定存在一个ai的第k位为1,作为先手,只需要取出ai-(ai ^ x)个物品,就可以使得下次异或的结果为0,让后手必输,那么先手必胜!
于是得到结果:最开始的时候,所有值异或的结果不为0就先手必胜,否则先手必败!
#include <iostream>
using namespace std;
int n;
int main()
{
scanf("%d", &n);
int res = 0;
while (n -- )
{
int x;
scanf("%d", &x);
res ^= x;
}
if (res) puts("Yes");
else puts("No");
return 0;
}
2、集合-Nim游戏
#include <iostream>
#include <cstring>
#include <unordered_set>
using namespace std;
const int N = 110, M = 100010;
int s[N], f[M];
int n,m;
int sg(int x)
{
if (f[x] != -1) return f[x];
unordered_set<int> S;
for(int i = 0; i < m; i ++ )
{
int sum = s[i];
if (x >= sum) S.insert(sg(x-sum));
}
for(int i = 0; ; i ++ )
if (!S.count(i))
return f[x] = i;
}
int main()
{
cin >> m;
for (int i = 0; i < m; i ++ ) cin >> s[i];
cin >> n;
memset(f, -1, sizeof f);
int res = 0;
for (int i = 0; i < n; i ++ )
{
int x;
cin >> x;
res ^= sg(x);
}
if (res) puts("Yes");
else puts("No");
return 0;
}
十一、莫比乌斯反演
!!!注意:代码中的qpow()是指快速幂函数