https://www.acwing.com/video/27/
数论
https://zhuanlan.zhihu.com/p/35060143
质数
定义:在大于1的整数中,如果只包含1和本身这两个约数,就被称为质数,或者叫素数。
(1)质数的判定————试除法 O(sqrt(n))
for循环的判定推荐用 i <= n / i;不用 i * i <= n 或者 i <= sqrt(n)。
所以它的时间一定是sqrt(n)
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)分解质因数————试除法 O(sqrt(n))
从小到大尝试所有因数,n中最多只包含一个大于sqrt(n)的质因子,可以像之前的那样判断到根号n,最后单独判断根号n是不是因子
时间不一定是sqrt(n),时间复杂度在O(logn)-O(sqrt(n))之间。
把x分解为 ,
都是质数。
void divide(int x){
for(int i = 2; i <= x / i; i++){
if (x % i == 0){ //一定是质数
int t = 0;
while(x % i == 0){
x /= i;
t++;
}
cout << i << ' ' << t << endl;
}
}
if(x > 1) cout << x << ' ' << 1 << endl;
cout << endl;
}(3)筛质数
朴素筛法、埃氏筛法和线性筛法,视频下面的评论也有总结
https://blog.csdn.net/Satur9/article/details/104229407
朴素筛法 O(n * logn)
把1~n中2的倍数,3的倍数, 4的倍数……都删除,那么剩下的数就都是质数了。
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ ) {
if (!st[i]){
primes[cnt ++ ] = i;
}
for (int j = i + i; j <= n; j += i) st[j] = true;
}
}埃氏筛法 O(nlog(logn))
我们会发现4的倍数也是2的倍数,所有我们只需要把所有质数的倍数删除就好。
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ ) {
if (!st[i]){
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i) st[j] = true;
}
}
}线性法 O(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] <= n / i; j++){
st[primes[j] * i] = true;
if(i % primes[j] == 0) break;
}
}
}定理: 1-n中有个质数。
总结:
1.普通的筛素数的原理是一个素数的倍数必须不是素数。
2.改进的筛素数的原理是每个合数必有一个最小素因子,根据每个最小素因子去访问合数就能防止合数被重复访问。
约数
(1)试除法求一个数的所有约数
vector<int> get_divisors(int x){
vector<int> res;
for(int i = 1; i <= x / i; i++){
if(x % i == 0){
res.push_back(i);
if(i != x / i) res.push_back(x / i);
}
}
sort(res.begin(), res.end());
return res;
}(2)约数个数
约数个数 视频的第48分钟左右
这里题目是要求n个数的乘积的约数个数,如果先求乘积会爆int,所以直接求所有数的质因数和他们的个数1,再用公式。
int main(){
int n;
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 p : primes) res = res * (p.second + 1) % mod;
cout << res << endl;
return 0;
}(3)约数之和
求primes和上一题一样,最后res的求法不一样。
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;
return 0;
(4)欧几里得算法
a 和 b 的公约数等于 b 和 a mod b 的公约数。任意整数与0的最大公约数是这个整数。
int gcd(int a, int b){
return b ? gcd(b, a % b) : a; //b = 0时,a和b的公约数是a
}欧拉函数
求每个数的欧拉函数
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的欧拉函数之和(筛法)
int primes[N], cnt;
int euler[N];
bool st[N];
void get_eulers(int n){
euler[1] = 1;
for(int i = 2; i <= n; i++){
if (!st[i]){
primes[cnt++] = i;
euler[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j ++ ){
int t = primes[j] * i;
st[t] = true;
if (i % primes[j] == 0){
euler[t] = euler[i] * primes[j];
break;
}
euler[t] = euler[i] * (primes[j] - 1);
}
}
}快速幂
求 mod p 朴素做法 O(k)
快速幂的核心思路是反复平方
把k拆成2的幂次之和
int qmi(int a, int b, int p){
LL res = 1 % p;
while(b){
if(b & 1) res = res * a % p;
a = a * (LL)a % p;
b >>= 1;
}
return res;
}if(b & 1) 判断是不是奇数 跟if(n%2==1)这个是一样的
n&1,将n变成二进制 跟 二进制的1 00000000 00000001按位做与操作。这时,只要n的最右边一位是1,结果就不是0,为true,
提问:LL res = 1 % p; 和 写成 LL res = 1 是有什么区别吗?
答:p = 1, b = 0的时候,第一种结果是0,第二种结果是1。(也就是 1 % 1 = 0)
快速幂求逆元
同余式是数论的基本概念之一,设m是给定的一个正整数,a、b是整数,若满足m|(a-b),则称a与b对模m同余,记为a≡b(mod m),或记为a≡b(m)。
称 x 为 b 的模 m 乘法逆元
性质:
费马小定理:
所以实质是求 ,所以
if(a % p == 0) puts("impossible");
else cout << qmi(a, p - 2, p) << endl;扩展欧几里得算法
https://blog.csdn.net/destiny1507/article/details/81750874
扩展欧几里得算法
这里扩展gcd,直接递归求出x,y。
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;
}
线性同余
用exgcd得到 ,要求出
,需要在第一个式子左右两边乘上
。
int a, b, m, x, y;
cin >> a >> b >> m;
int d = exgcd(a, m, x, y);
if (b % d) puts("impossible");
else printf("%d\n", (LL)b / d * x % m);
中国剩余定理
比较复杂,有空可以看看,在week6习题课,或者可以直接看题目对应的视频如下
https://www.acwing.com/video/273/
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL &x, LL &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;
LL x = 0, m1, a1;
cin >> m1 >> a1;
for (int i = 0; i < n - 1; i ++ ){
LL m2, a2;
cin >> m2 >> a2;
LL k1, k2;
LL d = exgcd(m1, -m2, k1, k2);
if ((a2 - a1) % d){
x = -1;
break;
}
k1 *= (a2 - a1) / d;
k1 = (k1 % (m2/d) + m2/d) % (m2/d);
x = k1 * m1 + a1;
LL m = abs(m1 / d * m2);
a1 = k1 * m1 + a1;
m1 = m;
}
if (x != -1) x = (x % m1 + m1) % m1;
cout << x << endl;
return 0;
}高斯消元
用于解方程组。在n³的时间内解含n个未知数的n个多元线性方程组。
结果:无解、无穷多组解、唯一解
https://blog.csdn.net/hellocsz/article/details/90488430
初等行列变换:
- 把某一行乘一个非零的数
- 交换某两行
- 把某行的若干倍加到另一行
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
const double eps = 1e-6;
int n;
double a[N][N];
int gauss(){
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; //最大系数为0
//交换到上面
for(int i = c; i < n + 1; i++ ) swap(a[t][i], a[r][i]);
//把系数变为1
for(int i = n; i >= c; i--) a[r][i] /= a[r][c];
//将下面所有行的第c列消为0
for(int i = r + 1; i < n; i++){
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[j][n] * a[i][j];
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 == 0){
for(int i = 0; i < n; i++) printf("%.2lf\n", a[i][n]);
}else if(t == 1) puts("Infinite group solutions");
else puts("No solution");
return 0;
}应用更多的是高斯消元解异或线性方程组
https://www.acwing.com/activity/content/problem/content/954/
int gauss(){
int c, r;
for(c = 0, r = 0; c < n; c++){
int t = r;
for(int i = r; i < n; i++){
if(a[i][c]) t = i;
}
if(a[t][c] == 0) continue; //最大系数为0
//交换到上面
for(int i = c; i < n + 1; i++ ) swap(a[t][i], a[r][i]);
//把系数变为1 (0,1方程组 这一步不需要了)
//for(int i = n; i >= c; i-- a[r][i] /= a[r][c];
//将下面所有行的第c列消为0
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 ++ )
if(a[i][n])
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; //有唯一解
}组合计数
有递推公式:
求
&preview=true)
(1) 方法一:1 ≤ n ≤ 10000, 1 ≤ b ≤ a ≤ 2000
#include <bits/stdc++.h>
using namespace std;
const int N = 2010, mod = 1e9 + 7;
int n;
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] + c[i - 1][j - 1]) % mod;
}
}
}
int main(){
init();
cin >> n;
while(n--){
int a, b;
cin >> a >> b;
printf("%d\n", c[a][b]);
}
return 0;
}(2) 方法二: 1 ≤ n ≤ 10000, 1 ≤ b ≤ a ≤ 
用快速幂求逆元
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010, mod = 1e9 + 7;
int fact[N], infact[N];
int qmi(int a, int b, int p){
int res = 1;
while(b){
if(b & 1) res = res * (LL)a % p;
a = a * (LL)a % p;
b >>= 1;
}
return res;
}
int main(){
fact[0] = infact[0] = 1;
for(int i = 1; i < N; i++){
fact[i] = (LL)fact[i - 1] * i % mod;
infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
int n;
cin >> n;
while(n--){
int a, b;
cin >> a >> b;
printf("%d\n", (LL)fact[a] * infact[b] % mod * infact[a - b] % mod);
}
return 0;
}
(3) 方法三:1 ≤ n≤ 20, 1 ≤ b ≤ a ≤
, 1 ≤ p ≤ 
卢卡斯定理 https://baike.baidu.com/item/lucas/4326261?fr=aladdin
会用这个结论就行
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int qmi(int a, int b, int p){
int res = 1;
while(b){
if(b & 1) res = res * (LL)a % p;
a = a * (LL) a % p;
b >>= 1;
}
return res;
}
int C(int a, int b, int p){
if (b > a) return 0;
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) % p;
}
return res;
}
int lucas(LL a, LL b, int p){
if (a < p && b < p) return C(a, b, p);
return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
int main(){
int n;
cin >> n;
while(n--){
LL a, b;
int p;
cin >> a >> b >> p;
printf("%d\n", lucas(a, b, p));
}
return 0;
}(4) 1 ≤ b ≤ a ≤ 5000 不取模,需进行高精度运算
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
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] <= n / i; j++){
st[primes[j] * i] = true;
if(i % primes[j] == 0) break;
}
}
}
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;
}
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);
}
vector<int> res;
res.push_back(1);
for (int i = 0; i < cnt; i ++ )
for (int j = 0; j < sum[i]; j ++ )
res = mul(res, primes[i]);
for (int i = res.size() - 1; i >= 0; i -- ) printf("%d", res[i]);
puts("");
return 0;
}(5) 满足条件的01序列
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010, mod = 1e9 + 7;
int qmi(int a, int b, int p){
int res = 1;
while(b){
if(b & 1) res = res * (LL)a % p;
a = a * (LL)a % p;
b >>= 1;
}
return res;
}
int main(){
int n;
cin >> n;
int a = n * 2, b = n;
int res = 1;
for (int i = a; i > a - b; i -- ) res = (LL)res * i % mod;
for (int i = 1; i <= b; i ++ ) res = (LL)res * qmi(i, mod - 2, mod) % mod;
res = (LL)res * qmi(n + 1, mod - 2, mod) % mod;
cout << res << endl;
return 0;
}容斥原理
求能被整除的数 暴力算法O(nm)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 20;
int p[N];
int main(){
int n, m;
cin >> n >> m;
for(int i = 0; i < m; i++) cin >> p[i];
int res = 0;
for(int i = 1; i < 1 << m; i++){
int t = 1, s = 0;
for(int j = 0; j < m; j++){
if(i >> j & 1){
if((LL)t * p[j] > n){
t = -1;
break;
}
t *= p[j];
s++;
}
}
if(t != -1){
if(s % 2) res += n / t;
else res -= n / t;
}
}
cout << res << endl;
return 0;
}简单博弈论
Nim游戏
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
int res = 0;
while(n--){
int x;
cin >> x;
res ^= x;
}
if (res) puts("Yes");
else puts("No");
return 0;
}台阶-Nim游戏
一个简单变种
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
cin >> n;
int res = 0;
for(int i = 1; i <= n; i++){
int x;
cin >> x;
if (i & 1) res ^= x;
}
if (res) puts("Yes");
else puts("No");
return 0;
}集合-Nim游戏
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 10010;
int n, m;
int s[N], f[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;
}拆分-Nim游戏
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
int f[N];
int sg(int x){
if(f[x] != -1) return f[x];
unordered_set<int> S;
for (int i = 0; i < x; i ++ )
for (int j = 0; j <= i; j ++ )
S.insert(sg(i) ^ sg(j));
for(int i = 0; ; i ++)
if(!S.count(i))
return f[x] = i;
}
int main(){
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;
}
京公网安备 11010502036488号