文章目录

  • 数学知识
    • AcWing 866. 试除法判定质数
    • AcWing 867. 分解质因数
    • AcWing 868. 筛质数
    • AcWing 869. 试除法求约数
    • AcWing 870. 约数个数
    • AcWing 871. 约数之和
    • AcWing 872. 最大公约数
    • AcWing 873. 欧拉函数
    • AcWing 874. 筛法求欧拉函数
    • AcWing 875. 快速幂
    • AcWing 876. 快速幂求逆元
    • AcWing 877. 扩展欧几里得算法
    • AcWing 878. 线性同余方程
    • AcWing 204. 表达整数的奇怪方式
    • AcWing 883. 高斯消元解线性方程组
    • AcWing 884. 高斯消元解异或线性方程组
    • AcWing 885. 求组合数 I
    • AcWing 886. 求组合数 II
    • AcWing 887. 求组合数 III
    • AcWing 888. 求组合数 IV
    • AcWing 889. 满足条件的01序列
    • AcWing 890. 能被整除的数
    • AcWing 891. Nim游戏
    • AcWing 892. 台阶-Nim游戏
    • AcWing 893. 集合-Nim游戏
    • AcWing 894. 拆分-Nim游戏
  • 动态规划
    • AcWing 2. 01背包问题
    • AcWing 3. 完全背包问题
    • AcWing 4. 多重背包问题
    • AcWing 5. 多重背包问题 II
    • AcWing 9. 分组背包问题
    • AcWing 898. 数字三角形
    • AcWing 895. 最长上升子序列
    • AcWing 896. 最长上升子序列 II
    • AcWing 897. 最长公共子序列
    • AcWing 902. 最短编辑距离
    • AcWing 899. 编辑距离
    • AcWing 282. 石子合并
    • AcWing 900. 整数划分
    • AcWing 338. 计数问题
    • AcWing 291. 蒙德里安的梦想
    • AcWing 91. 最短Hamilton路径
    • AcWing 285. 没有上司的舞会
    • AcWing 901. 滑雪
  • 贪心
    • AcWing 905. 区间选点
    • AcWing 908. 最大不相交区间数量
    • AcWing 906. 区间分组
    • AcWing 907. 区间覆盖
    • AcWing 148. 合并果子
    • AcWing 913. 排队打水
    • AcWing 104. 货仓选址
    • AcWing 125. 耍杂技的牛
  • 时空复杂度分析

数学知识

AcWing 866. 试除法判定质数

给定 n 个正整数 ai,判定每个数是否是质数。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个正整数 ai。

输出格式
共 n 行,其中第 i 行输出第 i 个正整数 ai 是否为质数,是则输出 Yes,否则输出 No。

数据范围
1≤n≤100,
1≤ai≤231−12^{31}−1231−1
输入样例:
2
2
6
输出样例:
Yes
No

is_pirme模板

#include<iostream>using namespace std;bool is_prime(int x)  // 判定质数
{if (x < 2) return false;for (int i = 2; i <= x / i; i ++ )if (x % i == 0)return false;return true;
}int main()
{int n, x;scanf("%d", &n);while(n --){scanf("%d", &x);if(is_prime(x)) puts("Yes");else puts("No");}return 0;
}

AcWing 867. 分解质因数

给定 n 个正整数 ai,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个正整数 ai。

输出格式
对于每个正整数 ai,按照从小到大的顺序输出其分解质因数后,每个质因数的底数和指数,每个底数和指数占一行。

每个正整数的质因数全部输出完毕后,输出一个空行。

数据范围
1≤n≤100,
2≤ai≤2×10910^9109
输入样例:
2
6
8
输出样例:

2 1
3 1

2 3

divide-朴素版枚举-按顺序试除

#include <iostream>using namespace std;void divide(int x) //朴素枚举-按顺序试除
{for (int i = 2; i <= x / i; i ++ )if (x % i == 0){int s = 0;while (x % i == 0) x /= i, s ++ ; //把i这个因子除尽, 统计i因子的个数printf("%d %d\n", i, s);}if (x > 1) printf("%d %d\n", i, s);//遍历完 x > 1,说明存在大于sqrt(n)的唯一质因子。puts("");
}int main()
{int n;scanf("%d", &n);while (n -- ){int x;scanf("%d", &x);divide(x);}return 0;
}

AcWing 868. 筛质数

给定一个正整数 n,请你求出 1∼n 中质数的个数。

输入格式
共一行,包含整数 n。

输出格式
共一行,包含一个整数,表示 1∼n 中质数的个数。

数据范围
1≤n≤10610^6106
输入样例:
8
输出样例:
4

好总结

线性筛法核心操作分析-两种情况:
i % primes[j] == 0 primes[j]一定是i的最小质因子, 则primes[j]一定是primes[j] * i的最小质因子
i % primes[j] != 0 此时primes[j] 一定小于i的所有质因子,此时primes[j]也一定是primes[j] * i的最小质因子
综上:primes[j]一定是primes[j] * i的最小质因子:筛去primes[j] * i 标记合数为true


从小到大枚举现有质数 <= n / i : for (int j = 0; primes[j] <= n / i; j ++ )

线性筛法:质数打表【枚举区间所有质数】

#include<iostream>using namespace std;const int N = 1e6 + 10;//空间不够大: Segmentation Fault 【段错误:输出的数值等】  int n;int primes[N], cnt;//primes[] : 存质因数数组 ;  cnt:移动下标指针
bool st[N];//标记是否被筛选 : 合数被筛选标记为 truevoid get_primes(int n)//线性筛法 -- 埃式筛法的优化 O(nlogn)
{for (int i = 2; i <= n; i ++ ) //初始从质数2开始枚举{if (!st[i]) primes[cnt ++ ] = i; //没有被筛过必为质数, cnt统计质数个数for (int j = 0; primes[j] <= n / i; j ++ ) //【从小到大枚举现有质数】筛到现有的质数且质数大小 <= n / i {   st[primes[j] * i] = true;//筛去现有质数的倍数, 标记为合数 (用已有质数枚举:一定构成新的合数值:不会重复筛-优化)if (i % primes[j] == 0) break; //此时primes[j]一定是i的最小质因子}}
}int main()
{scanf("%d", &n);get_primes(n);printf("%d", cnt);return 0;
}

朴素筛法

//朴素筛法 - 埃式筛法【鉴于时间复杂度:算法题就不用此筛法啦】void get_primes(int n)
{for (int i = 2; i <= n; i ++ )//对所有数的倍数筛除{if (st[i]) continue;primes[cnt ++ ] = i;for (int j = i + i; j <= n; j += i)st[j] = true;}
}

AcWing 869. 试除法求约数

给定 n 个正整数 ai,对于每个整数 ai,请你按照从小到大的顺序输出它的所有约数。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个整数 ai。

输出格式
输出共 n 行,其中第 i 行输出第 i 个整数 ai 的所有约数。

数据范围
1≤n≤100,
2≤ai≤2×10910^9109
输入样例:
2
6
8
输出样例:
1 2 3 6
1 2 4 8

数论知识:若 n % b == 0 ,则 n % (n / b) == 0 : 即n整除b, 则n也整除 nb\bf\frac{n}{b}bn​
[可以简单的逆推 : b * (n / b) == n一对因子]

#include <iostream>
#include <algorithm>
#include <vector>using namespace std;vector<int> get_divisors(int x)//试除法求约数
{vector<int> res;for (int i = 1; i <= x / i; i ++ )//每次枚举一对因子 :1和本身也是因子, 需从1开始枚举if (x % i == 0){res.push_back(i);if (i != x / i) res.push_back(x / i);//当对应的因子不相同时加入: i != x / i}sort(res.begin(), res.end());//按从小到大排序return res;
}int main()
{int n;scanf("%d", &n);while (n -- ){int x;scanf("%d", &x);auto res = get_divisors(x); //多次判断:每次重新定义数组for(auto x : res) printf("%d ", x);        puts("");}return 0;
}

AcWing 870. 约数个数

给定 n 个正整数 ai,请你输出这些数的乘积的约数个数,答案对 109+7 取模。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个整数 ai。

输出格式
输出一个整数,表示所给正整数的乘积的约数个数,答案需对 109+7 取模。

数据范围
1≤n≤100,
1≤ai≤2×10910^9109
输入样例:
3
2
6
8
输出样例:
12


n个数的乘积的约数个数 - 公式
代码思想:读入n个数 : 分别计算每个数的质因子: 统计n个数的质因子【hash表:primes】
约数公式推导
原理:每个数可以拆解为n个质因子的乘积, 而质因子之间相乘得到的数就是一种因子
【n个数的乘积的值对应的约数个数 == (质因数指数 + 1)的乘积】

$x = a_1^{p_1} * a_2^{p_2} * a_3^{p_3} * a_4^{p_4} …* a_n^{p_n} $
即x分解成:∑质因数ai指数pi===>x的约数个数=∏(pi+1)即x分解成:\sum质因数a_{i}^{指数p_i} ===> \color{blue}{x的约数个数 = \prod (p_i + 1)}即x分解成:∑质因数ai指数pi​​===>x的约数个数=∏(pi​+1)

#include <iostream>
#include <algorithm>
#include <unordered_map>//可以映射和下标查找  hash[first] = second;
#include <vector>#define x first//unordered_map -- hash表 :可化简代码
#define y secondusing namespace std;typedef long long LL; //不开long long见祖宗const int N = 110, mod = 1e9 + 7;int main()
{int n;cin >> n;//【unordered_map可以用first和second】unordered_map<int, int> primes;//hash表存质因子 (质因数, 指数)while (n -- )//分别求n个数的质因子:等效先拆分再计算各部分{int x;scanf("%d", &x); //读入n个数 : 分别计算每个数的质因子: 统计n个数的质因子【hash表】for (int i = 2; i <= x / i; i ++ )//计算质因子及其个数//while(x % i == 0) x /= i, primes[i]++; 合并写法while (x % i == 0)   {x /= i;primes[i] ++ ;}if (x > 1) primes[x] ++ ;}LL res = 1;for (auto p : primes) res = res * (p.y + 1) % mod;//公式cout << res << endl;return 0;
}

divide函数封装版

#include <iostream>
#include <algorithm>
#include <unordered_map>//可以映射和下标查找  hash[first] = second;
#include <vector>#define x first//unordered_map -- hash表 :可化简代码
#define y secondusing namespace std;typedef long long LL;const int N = 110, mod = 1e9 + 7;unordered_map<int , int > primes; //【可以用first和second】void divide(int x)
{for(int i = 2; i <= x / i; i++){while(x % i == 0) x /= i, primes[i] ++; //统计n个数的乘积因子个数}if(x > 1) primes[x] ++;
}int main()
{int n;scanf("%d", &n);while (n -- ) //n个数乘积的约数个数{int x;scanf("%d", &x);divide(x);}LL res = 1;for (auto p : primes) res = res * (p.y + 1) % mod;//质因数 * (对应个数 + 1)  的乘积之和printf("%lld", res);return 0;
}

稍长版

void divide(int x)
{for(int i = 2; i <= x / i; i++){if(x % i == 0){int s = 0;while(x % i == 0) x /= i, s++;primes[i] += s; //统计n个数的乘积因子个数}}if(x > 1) primes[x] ++;
}

AcWing 871. 约数之和

给定 n 个正整数 ai,请你输出这些数的乘积的约数之和,答案对 109+7 取模。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个整数 ai。

输出格式
输出一个整数,表示所给正整数的乘积的约数之和,答案需对 109+7 取模。

数据范围
1≤n≤100,
1≤ai≤2×10910^9109
输入样例:
3
2
6
8
输出样例:
252

x的约数之和=(p10∗p11∗p12∗...p1α1)...(pk0∗pk1∗pk2∗...pkαk)x的约数之和 = (p_1^0 * p_1^1 * p_1^2 * ... p_1^{\alpha_1}) ... (p_k^0 * p_k^1 * p_k^2 * ... p_k^{\alpha_k})x的约数之和=(p10​∗p11​∗p12​∗...p1α1​​)...(pk0​∗pk1​∗pk2​∗...pkαk​​)
【暴力证明】: 展开式相等, 展开后每一项对应一个约数

divide简化版

#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
#define x first
#define y secondusing namespace std;typedef long long LL; const int N = 110, mod = 1e9 + 7;int main()
{int n;scanf("%d", &n);unordered_map<int, int> primes; //哈希表优化:约数以及其个数while (n -- ) {int x;scanf("%d", &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 i : primes) {LL p = i.x, a = i.y;  //p:质因数 a:p指数LL t = 1;while(a --) t = (t * p + 1) % mod; //秦九韶公式 :得到t = pi^0 * pi^1 * pi^2 * ... pi^{a_k}res = res * t % mod; }printf("%lld", res);return 0;
}
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
#define x first
#define y secondusing namespace std;typedef long long LL; const int N = 110, mod = 1e9 + 7;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 i : primes) {LL p = i.x, a = i.y;  //p:质因数 a:p指数LL t = 1;while (a -- ) t = (t * p + 1) % mod; //秦九韶公式 :得到t = pi^0 * pi^1 * pi^2 * ... pi^{a_k}res = res * t % mod; }cout << res << endl;return 0;
}

AcWing 872. 最大公约数

给定 n 对正整数 ai,bi,请你求出每对数的最大公约数。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个整数对 ai,bi。

输出格式
输出共 n 行,每行输出一个整数对的最大公约数。

数据范围
1≤n≤105,
1≤ai,bi≤2×10910^9109
输入样例:
2
3 6
4 6
输出样例:
3
2

gcd模板 + algorithm有__gcd(a, b)

c[n][m] = c[n][n - m]

#include<iostream>
#include<algorithm>  // __gcd(a, b)
using namespace std;int gcd(int a, int b)  // 欧几里得算法
{return b ? gcd(b, a % b) : a;
}int main()
{int n;scanf("%d", &n);int a, b;while( n --){scanf("%d%d", &a, &b);printf("%d\n", __gcd(a, b));}return 0;
}

练手-不重要哦

int gcd(int a, int b)
{//if(a % b == 0) return b;//else gcd(b, a % b); //return 加不加都行, 递归最后都等效返回return b //return a % b == 0 ? b : gcd(b, a % b);//y总写法 //    if(b == 0 ) return a;//   else return gcd(b, a % b);//return b ? gcd(b, a % b) : a;}

天梯简写:调用库
最大公约数 , 最小公倍数

#include<bits/stdc++.h>using namespace std;int a, b;int main()
{scanf("%d%d", &a, &b);int c = __gcd(a, b);//简化代码思想 printf("%d\n", c);//最大公约数 printf("%d\n", a * b / c);//最小公倍数
}

AcWing 873. 欧拉函数

给定 n 个正整数 ai,请你求出每个数的欧拉函数。

欧拉函数的定义

1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)\phi(N)ϕ(N)
若在算数基本定理中,N=p1a1p2a2p3a3...pmam\color{black}{N= p_1^{a_1}p_2^{a_2}p_3^{a_3}...p_m^{a_m}}N=p1a1​​p2a2​​p3a3​​...pmam​​ 则:
ϕ(N)=N×p1−1p1×p2−1p2×p3−1p3×...×pm−1pm{\color{black}{\phi(N) = N × \frac{p_1-1}{p_1} × \frac{p_2-1}{p_2} × \frac{p_3-1}{p_3} × ... ×\frac{p_m-1}{p_m}}}ϕ(N)=N×p1​p1​−1​×p2​p2​−1​×p3​p3​−1​×...×pm​pm​−1​

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个正整数 ai。

输出格式
输出共 n 行,每行输出一个正整数 ai 的欧拉函数。

数据范围
1≤n≤100,
1≤ai≤2×10910^9109
输入样例:
3
3
6
8
输出样例:
2
2
4

欧拉函数的定义

1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)\phi(N)ϕ(N)
若在算数基本定理中,N=p1a1p2a2p3a3...pmam\color{orange}{N= p_1^{a_1}p_2^{a_2}p_3^{a_3}...p_m^{a_m}}N=p1a1​​p2a2​​p3a3​​...pmam​​ 则:
ϕ(N)=N×p1−1p1×p2−1p2×p3−1p3×...×pm−1pm\large{\color{blue}{\phi(N) = N × \frac{p_1-1}{p_1} × \frac{p_2-1}{p_2} × \frac{p_3-1}{p_3} × ... ×\frac{p_m-1}{p_m}}}ϕ(N)=N×p1​p1​−1​×p2​p2​−1​×p3​p3​−1​×...×pm​pm​−1​

#include <iostream>using namespace std;int phi(int x)
{int res = x;for (int i = 2; i <= x / i; i ++ )if (x % i == 0){res = res / i * (i - 1); //N * (每一项 = / 质因子 * 质因子 - 1) 【防溢出写法】while (x % i == 0) x /= i; //把i这个质因子除尽}if (x > 1) res = res / x * (x - 1); //最后可能有很大的质因子,不要漏了!!!return res;
}int main()
{int n;cin >> n;while (n -- ){int x;cin >> x;cout << phi(x) << endl;}return 0;
}

AcWing 874. 筛法求欧拉函数

给定一个正整数 n,求 1∼n 中每个数的欧拉函数之和。

输入格式
共一行,包含一个整数 n。

输出格式
共一行,包含一个整数,表示 1∼n 中每个数的欧拉函数之和。

数据范围
1≤n≤10610^6106
输入样例:
6
输出样例:
12

求1~N中每个数的欧拉函数 - 线性筛法打表
①ϕ(N)=N×p1−1p1×p2−1p2×p3−1p3×...×pm−1pm\large{\color{pink}{\phi(N) = N × \frac{p_1-1}{p_1} × \frac{p_2-1}{p_2} × \frac{p_3-1}{p_3} × ... ×\frac{p_m-1}{p_m}}}ϕ(N)=N×p1​p1​−1​×p2​p2​−1​×p3​p3​−1​×...×pm​pm​−1​
展开式推导
i % primes[j] == 0 时:
ϕ(primes[j]∗i)=ϕ(i)∗(primes[j])\large{\color{blue}{\phi(primes[j] * i) =\phi(i) * (primes[j])} }ϕ(primes[j]∗i)=ϕ(i)∗(primes[j])
i % primes[j] != 0 时:
ϕ(primes[j]∗i)=ϕ(i)∗primes[j]∗primes[j]−1primes[j]\large{\color{lightblue}{\phi(primes[j] * i) = \phi(i) * primes[j] * \frac{primes[j] - 1}{primes[j]} } }ϕ(primes[j]∗i)=ϕ(i)∗primes[j]∗primes[j]primes[j]−1​
ϕ(primes[j]∗i)=ϕ(i)∗(primes[j]−1)\large{\color{blue}{\phi(primes[j] * i) =\phi(i)* (primes[j] - 1)}}ϕ(primes[j]∗i)=ϕ(i)∗(primes[j]−1)

1~N:每轮的N为自身数值 在[1, 自身值]区间: 质数互质的个数 = 自身值 - 1
代码线性筛法改造 phi[1] = 1, phi[2] = 1, phi[3] = 2, phi[5] = 4; 【与质数互质的个数 = 每轮N - 1
int t = primes[j] * i; (简化代码)

#include <iostream>using namespace std;typedef long long LL;const int N = 1000010;int primes[N], cnt;
int phi[N]; //存数值为[1, N]的欧拉函数(与其互质的个数)
bool st[N];void get_phis(int n)  //线性筛法 + 按情况插入公式
{phi[1] = 1;//【注意因为phi从2开始计算, 需先初始化phi[1]】 而1~1中和1互质的个数为1 for (int i = 2; i <= n; i ++ ){if (!st[i]){primes[cnt ++ ] = i;phi[i] = i - 1; //在[1, 自身值]区间: 质数互质的个数 = 自身值 - 1}for (int j = 0; primes[j] <= n / i; j ++ ){int t = primes[j] * i;st[t] = true;if (i % primes[j] == 0) //情况1{ phi[t] = phi[i] * primes[j]; //公式②break; //线性筛法break优化}else phi[t] = phi[i] * (primes[j] - 1);  //情况2【i % primes[j] != 0 】- 公式③ }}
}int main()
{int n;cin >> n;get_phis(n);LL res = 0;for (int i = 1; i <= n; i ++ ) res += phi[i];cout << res << endl;return 0;
}

y总推导

AcWing 875. 快速幂

给定 n 组 ai,bi,pi,对于每组数据,求出 abiimodpi 的值。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含三个整数 ai,bi,pi。

输出格式
对于每组数据,输出一个结果,表示 abiimodpi 的值。

每个结果占一行。

数据范围
1≤n≤100000,
1≤ai,bi,pi≤2×10910^9109
输入样例:
2
3 2 5
4 3 9
输出样例:
4
1

a *= a 不完全等价于a = a * a 【原因:类型转化精度变化造成数据不精确】 ==> 有不同类型时就老实使用a = a * a

#include<iostream>using namespace std;typedef long long LL;LL qmi(int a, int k, int p)
{LL res = 1 % p;//乘积基数为1   !!!!!!!!!!    [有个坑如 p = 1 时, 1 % 1 = 0]while(k){   //LL * int 自动类型转换if(k & 1)  res = res * a % p;//k & 1   ==  k % 2   判断是否为奇数    a = a *(LL)a  % p;k >>= 1;}return res;
}int main()
{int n;scanf("%d", &n);int a, k, p;while(n --){scanf("%d%d%d", &a, &k, &p);printf("%lld\n", qmi(a, k, p));}return 0;
}

mycode2022【全LL版】

#include<iostream>using namespace std;typedef long long LL;LL qmi(LL a, LL k, LL p)
{LL res = 1 % p;while(k){if(k & 1) res = res * a % p;a = a * a % p;k >>= 1;}return res % p;
}signed main()
{LL n; //int可以直接%lld读入 - 类型转换scanf("%lld", &n); LL a, k, p; //LL -- %lldwhile(n --){scanf("%lld%lld%lld", &a, &k, &p); //也可以int -- %dprintf("%lld\n", qmi(a, k, p)); //注意%lld}return 0;
}

AcWing 876. 快速幂求逆元

给定 n 组 ai,pi,其中 pi 是质数,求 ai 模 pi 的乘法逆元,若逆元不存在则输出 impossible。

注意:请返回在 0∼p−1 之间的逆元。

乘法逆元的定义

若整数 b,m 互质,并且对于任意的整数 a,如果满足 b∣ab~|~ab ∣ a,则存在一个整数 x,使得 a/b≡a×x(modm)a/b≡a×x(mod~m)a/b≡a×x(mod m),则称 x 为 b 的模 m 乘法逆元,记为 b−1(modm)b−1(mod~m)b−1(mod m)。
b 存在乘法逆元的充要条件是 b 与模数 m 互质。当模数 m 为质数时,bm−2b^{m−2}bm−2 即为 bbb 的乘法逆元。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一个数组 ai,pi,数据保证 pi 是质数。

输出格式
输出共 n 行,每组数据输出一个结果,每个结果占一行。

若 ai 模 pi 的乘法逆元存在,则输出一个整数,表示逆元,否则输出 impossible。

数据范围
1≤n≤10510^5105,
1≤ai,pi≤2∗10910^9109
输入样例:
3
4 3
8 5
6 3
输出样例:
1
2
impossible


逆元计算原理(找到一个数x满足①式)

①ab≡a∗x\color{bule}{ \huge{\frac{a}{b} \equiv a*x } }ba​≡a∗x (mod m)

②ab≡a∗b−1\huge{\frac{a}{b} \equiv a*b^{-1} }ba​≡a∗b−1 (mod m)

两边同乘b:
③b∗ab≡a∗b−1∗b\huge{b * \frac{a}{b} \equiv a*b^{-1} * b }b∗ba​≡a∗b−1∗b (mod m)
再消去a:

④b−1∗b≡1\huge{b^{-1} * b \equiv 1}b−1∗b≡1 (mod m)

费马小定理

举个例子 3∗3−1≡13 * 3^{-1} \equiv 13∗3−1≡1 (mod 5)
求3−13^{-1}3−1 (mod 5): 即35−23^{5-2} % 5 = 235−2 在mod 5中3的逆元为2 【3 * 2 % 5 == 1】

根据逆元计算原理推导:
设xxx为bbb的逆元,即b−1,则:b^{-1},则:b−1,则:

①b∗x≡1\huge{\color{bule}{b * x \equiv 1} }b∗x≡1 (mod p)
②bp−1≡1\huge{b^{p-1} \equiv 1 }bp−1≡1 (mod p)
拆解②:
③b∗bp−2≡1\huge{b * b^{p-2} \equiv 1 }b∗bp−2≡1 (mod p)
即b的逆元x=bp−2(modp)\color{red}{\huge{即b的逆元x = b^{p-2} }(mod~p) }即b的逆元x=bp−2(mod p)


#include <iostream>
#include <algorithm>using namespace std;typedef long long LL;LL qmi(int a, int k, int p) //快速幂
{LL res = 1 % p;  //【注意返回值long long!!!】依题%p, 若没有限制%大数while (k){if (k & 1) res = res * a % p;a = a * (LL)a % p;k >>= 1;}return res;
}int main()
{int n;scanf("%d", &n);while (n -- ){int a, p;scanf("%d%d", &a, &p);if (a % p == 0) puts("impossible"); //a的逆元在(mod p)存在的条件:gcd(a, p) == 1; 【互质】else printf("%lld\n", qmi(a, p - 2, p)); //a的逆元 = a^{p-2} % p }return 0;
}

mycode2022

LL qmi(int a, int k, int p) //快速幂
{LL res = 1 % p; //小心【p == 1】while(k){if(k & 1) res = res * a % p;a = (LL)a * a % p;k >>= 1;}return res;
}

AcWing 877. 扩展欧几里得算法

给定 n 对正整数ai,bia_i,b_iai​,bi​,对于每对数,求出一组 xi,yix_i,y_ixi​,yi​,使其满足 ai×xi+bi×yi=gcd(ai,bi)a_i×x_i+b_i×y_i=gcd(a_i,b_i)ai​×xi​+bi​×yi​=gcd(ai​,bi​)。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含两个整数 ai,bia_i,b_iai​,bi​。

输出格式
输出共 n 行,对于每组 ai,bia_i,b_iai​,bi​,求出一组满足条件的 xi,yix_i,y_ixi​,yi​,每组结果占一行。

本题答案不唯一,输出任意满足条件的 xi,yix_i,y_ixi​,yi​均可。

数据范围
1≤n≤10510^5105,
1≤ai,bia_i,b_iai​,bi​≤2×10910^9109
输入样例:
2
4 6
8 18
输出样例:
-1 1
-2 1


斐蜀定理 :若 x∗a+y∗b=n倍的gcd(a,b),(n=1,2,3...)x * a + y * b = n倍的gcd(a, b) , (n = 1, 2, 3 ...)x∗a+y∗b=n倍的gcd(a,b),(n=1,2,3...)
必能找到一组系数解x, y 使得a与b最大公约数的倍数

a % b = a - ⌊ab⌋\lfloor \frac{a}{b} \rfloor⌊ba​⌋ * b = 余数r 【原理:a / b = ⌊ab⌋\lfloor \frac{a}{b} \rfloor⌊ba​⌋ + 余数r】 (注:⌊ab⌋\lfloor \frac{a}{b} \rfloor⌊ba​⌋为向下取整)

扩展欧几里得定理:用于求解方程 ax+by=gcd(a,b)ax + by = gcd(a,b)ax+by=gcd(a,b) 的系数解(x, y)
详细题解

推导:
ax+by=dax+by=dax+by=d

递归替换:by+(a%b)x=dby+(a\%b)x=dby+(a%b)x=d
余数替换:by+(a−a/b∗b)x=dby+(a-a/b*b)x=dby+(a−a/b∗b)x=d
整理系数:ax+b(y−a/b∗x)=dax+b(y-a/b*x)=dax+b(y−a/b∗x)=d
此时ax的系数x不变,by的系数y变成 y=y−a/b∗xy = y - a / b * xy=y−a/b∗x 即y−=a/b∗xy~~-= a~/~b * xy  −=a / b∗x

#include <iostream>
#include <algorithm>using namespace std;int exgcd(int a, int b, int &x, int &y)  //gcd的条件表达式展开①与②分类讨论 : b ? gcd(b, a % b) : a;
{if (!b) //① b == 0时显然系数(x, y) = (1, 0)是一组解{x = 1, y = 0;return a;}//② b != 0时, gcd用d表示   b * y + (a % b) * x == d , 根据公式 a%b的余数r为 a - a / b * b, 等式变为b * y + (a - a / b * b) * x == d                    int d = exgcd(b, a % b, y, x);    y -= a / b * x;return d; //返回最大公约数
}int main()
{int n;scanf("%d", &n);while (n -- ){int a, b;scanf("%d%d", &a, &b);int x, y;exgcd(a, b, x, y);printf("%d %d\n", x, y);}return 0;
}
//土话:a、b都是最大公约数的倍数,显然a * x + b * y = n * gcd(a, b) (等于最大公约数的倍数)

AcWing 878. 线性同余方程

给定 n 组数据 ai,bi,mia_i,b_i,m_iai​,bi​,mi​,对于每组数求出一个 xi,使其满足 ai×xi≡bi(modmi)a_i×x_i~≡~b_i(mod~m_i)ai​×xi​ ≡ bi​(mod mi​),如果无解则输出 impossible。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一组数据 ai,bi,mia_i,b_i,m_iai​,bi​,mi​。

输出格式
输出共 n 行,每组数据输出一个整数表示一个满足条件的 xix_ixi​,如果无解则输出 impossible。

每组数据结果占一行,结果可能不唯一,输出任意一个满足条件的结果均可。

输出答案必须在 int 范围之内。

数据范围
1≤n≤10510^5105,
1≤ai,bi,mia_i,b_i,m_iai​,bi​,mi​≤2×10910^9109
输入样例:
2
2 3 6
4 3 5
输出样例:
impossible
-3

AcWing 204. 表达整数的奇怪方式

给定 2n 个整数 a1,a2,…,an和m1,m2,…,mna_1,a_2,…,a_n 和 m_1,m_2,…,m_na1​,a2​,…,an​和m1​,m2​,…,mn​,求一个最小的非负整数 x,满足 ∀i∈[1,n],x≡mi(modai)∀_i∈[1,n],x≡m_i(mod~a_i)∀i​∈[1,n],x≡mi​(mod ai​)。

输入格式
第 1 行包含整数 n。

第 2…n+1 行:每 i+1 行包含两个整数 ai 和 mi,数之间用空格隔开。

输出格式
输出最小非负整数 x,如果 x 不存在,则输出 −1。
如果存在 x,则数据保证 x 一定在 64 位整数范围内。

数据范围
1≤aia_iai​≤231−12^{31}−1231−1,
0≤mim_imi​<aia_iai​
1≤n≤25
输入样例:
2
8 7
11 9
输出样例:
31

AcWing 883. 高斯消元解线性方程组

输入一个包含 n 个方程 n 个未知数的线性方程组。

方程组中的系数为实数。

求解这个方程组。

下图为一个包含 m 个方程 n 个未知数的线性方程组示例:

输入格式
第一行包含整数 n。

接下来 n 行,每行包含 n+1 个实数,表示一个方程的 n 个系数以及等号右侧的常数。

输出格式
如果给定线性方程组存在唯一解,则输出共 n 行,其中第 i 行输出第 i 个未知数的解,结果保留两位小数。

如果给定线性方程组存在无数解,则输出 Infinite group solutions。

如果给定线性方程组无解,则输出 No solution。

数据范围
1≤n≤100,
所有输入系数以及常数均保留两位小数,绝对值均不超过 100。

输入样例:
3
1.00 2.00 -1.00 -6.00
2.00 1.00 -3.00 -9.00
-1.00 -1.00 2.00 7.00
输出样例:
1.00
-2.00
3.00

首先有n个方程 系数aia_{i}ai​ 和 常数b 构造行列式
解有三种情况 : 唯一解, 无解, 无穷多组解[通解]
化成类似上三角行列式,化简过程判断解
若推出 0 == 非零(fabs(a[i][n]) > eps) 矛盾则无解

**线性代数-行列式-初等行变换 **
① 两方程互换,解不变
②一方程乘以非零数k,解不变
③一方程乘以数k加上另一方程,解不变

高斯消元-代码
枚举每一列c①找到绝对值最大的一行②将该行换到第一行 ③将该行的第c个数变成1  [此时第c行的方程系数处理完, 之后固定不变]④将其余所有行的第c列消成0

语法: **abs()**是返回整数的绝对值, **fabs()**返回浮点数的绝对值

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>using namespace std;const int N = 110;
const double eps = 1e-8;int n;
double a[N][N];int gauss()  // 高斯消元,答案存于a[i][n]中,0 <= i < n
{int c, r; //c列, r行for (c = 0, r = 0; c < n; c ++ ) //a_i ...a_{i + c} 每次消一个x的系数(my){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, 则不用进行化简为1的操作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   【 a[r][c] / a[r][c] = 1 】for (int i = r + 1; i < n; i ++ )  // 用当前行将下面其他行(r + 1开始)所有的第c列系数消成0if (fabs(a[i][c]) > eps) //若系数非零【消成第c列系数为0, 此行其他系数要相应变化!!!保持等价】for (int j = n; j >= c; j -- ) // 因为未固定的首行第c列的系数已经化为1 , 所以用【系数 - 1 * 系数 = 0 】a[i][j] -= a[r][j] * a[i][c];  //第i行的所有元素a[i][j] -= 首行所有元素a[r][j] * 第i行第c列元素的系数a[i][c]//【注意:要逆序!否则系数消为0, 则都为0】 (代码直接取系数位置a[r][j],不需要取1的位置)         // out(); 调试大法                             r ++ ; //化简后行数++}if (r < n) //化简后r < n行{for (int i = r; i < n; i ++ ) if (fabs(a[i][n]) > eps) // > eps浮点数非零 == 0矛盾  [浮点数判断为0有误差!]【规定若浮点数 < eps(极小值) ,则浮点数 == 0 】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()
{scanf("%d", &n);for (int i = 0; i < n; i ++ )for (int j = 0; j < n + 1; j ++ )scanf("%lf", &a[i][j]);int t = gauss();if (t == 2) puts("No solution");else if (t == 1) puts("Infinite group solutions");else{for (int i = 0; i < n; i ++ ) //输出解 a[i][n] , 注意浮动数的判0处理!!!{if (fabs(a[i][n]) < eps) a[i][n] = 0;  // 去掉输出 -0.00 的情况 【浮点数】printf("%.2lf\n", a[i][n]);}}return 0;
}

调式大法

void out()
{for(int i = 0; i < n; i++){for(int j = 0; j < n + 1; j++){printf("%10.2lf", a[i][j]);}puts("");}puts("");
}

AcWing 884. 高斯消元解异或线性方程组

输入一个包含 n 个方程 n 个未知数的异或线性方程组。

方程组中的系数和常数为 0 或 1,每个未知数的取值也为 0 或 1。

求解这个方程组。

异或线性方程组示例如下:

M[1][1]x[1] ^ M[1][2]x[2] ^ … ^ M[1][n]x[n] = B[1]
M[2][1]x[1] ^ M[2][2]x[2] ^ … ^ M[2][n]x[n] = B[2]
…
M[n][1]x[1] ^ M[n][2]x[2] ^ … ^ M[n][n]x[n] = B[n]

其中 ^ 表示异或(XOR),M[i][j] 表示第 i 个式子中 x[j] 的系数,B[i] 是第 i 个方程右端的常数,取值均为 0 或 1。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含 n+1 个整数 0 或 1,表示一个方程的 n 个系数以及等号右侧的常数。

输出格式
如果给定线性方程组存在唯一解,则输出共 n 行,其中第 i 行输出第 i 个未知数的解。

如果给定线性方程组存在多组解,则输出 Multiple sets of solutions。

如果给定线性方程组无解,则输出 No solution。

数据范围
1≤n≤100
输入样例:
3
1 1 0 1
0 1 1 0
1 0 0 1
输出样例:
1
0
0

AcWing 885. 求组合数 I

给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cabmod(109+7)C^b_a ~mod(10^9+7)Cab​ mod(109+7) 的值。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一组 a 和 b。

输出格式
共 n 行,每行输出一个询问的解。

数据范围
1≤n≤10000,
1≤b≤a≤2000
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1


dp法: Cab(a,b范围2000)C_a^b(a, b范围2000)Cab​(a,b范围2000)
注意:c[n][m]:m<=nc[n][m] :m <= nc[n][m]:m<=n
上一个状态选或不选两种情况转移到下一个状态

f[i][j]=f[i−1][j−1]+f[i−1][j];f[i][j] = f[i - 1][j - 1] + f[i - 1][j];f[i][j]=f[i−1][j−1]+f[i−1][j]; 特例 i - 1不从1开始: i从[0, N) :打表

#include <iostream>
#include<cstdio>//scanf与printfusing namespace std;const int N = 2010, mod = 1e9 + 7;int c[N][N];void init()//组合数dp含义:选或不选     【dp可开到c[2000][2000]】    估算空间复杂度 2^10 约为1000 好算
{   //c[0][0] = 1, 数组下标到N - 1for (int i = 0; i < N; i ++ )//打表初始区间[0, N-1]for (int j = 0; j <= i; j ++ )//c[i][j] 规定: j <= iif (!j) c[i][j] = 1;//定义c[i][0] = 1else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;//越界是可以,但不建议:如c[-1][-1]可以输出
}int main()
{int n;init();//初始化c[a][b]  : a,b ∈ [1, 2000]   scanf("%d", &n);while (n -- ){int a, b;scanf("%d%d", &a, &b);printf("%d\n", c[a][b]); //$C_a^b$}return 0;
}

AcWing 886. 求组合数 II

给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cabmod(109+7)C^b_a ~mod(10^9+7)Cab​ mod(109+7) 的值。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一组 a 和 b。

输出格式
共 n 行,每行输出一个询问的解。

数据范围
1≤n≤10000,
1≤b≤a≤10510^5105
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1


Cab(a,b在105级别)C_a^b(a,b在\red{10^5}级别)Cab​(a,b在105级别)

费马小定理:在(mod p)上: x的逆元: x−1=xp−2%px^{-1} = x^{p - 2} \% px−1=xp−2%p(mod p)

利用①组合数拆解公式 + ②阶乘预处理 + ③阶乘的逆元(费马小定理)
Cab=a!b!(a−b)!\huge{C_a^b = \frac{a!}{b!(a-b)!}}Cab​=b!(a−b)!a!​
Cab=fact(a)∗infact(b)∗infact(a−b)\huge{\color{blue}{C_a^b = fact(a) * infact(b) * infact(a - b) } }Cab​=fact(a)∗infact(b)∗infact(a−b)

fact(x): x的阶乘 infact(x): x阶乘的逆元 (mod 109+710^9 + 7109+7)
(可理解为fact(i)存i!,infact(i)存1i!)【预处理】强转(LL)\color{red}{ (可理解为fact(i)存i!, infact(i)存 \frac{1}{i!})【预处理】强转(LL) }(可理解为fact(i)存i!,infact(i)存i!1​)【预处理】强转(LL)
存数组:边界初始化fact[0]=infact[0]=1;【乘积因子1】fact[0] = infact[0] = 1; \color{green}{【乘积因子1】}fact[0]=infact[0]=1;【乘积因子1】

阶乘的逆元等效做分母 :举个例子 : 1/x=1∗1x=x−1\color{lightgreen}{1 / x = 1 * \frac{1}{x} = x^{-1}}1/x=1∗x1​=x−1
【做分母】 如 (x!)−1mod(109+7)=1x!mod(109+7)\huge{(x!)^{-1} mod(10^9+7) = \frac{1}{x!} mod(10^9+7)}(x!)−1mod(109+7)=x!1​mod(109+7)

#include <iostream>
#include <algorithm>using namespace std;typedef long long LL;const int N = 100010, mod = 1e9 + 7;int fact[N], infact[N];//阶乘 , 阶乘的逆元int qmi(int a, int k, int p)
{int res = 1 % p; //很重要! 比如p = 1的情况, res结果返回0while (k){if (k & 1) res = (LL)res * a % p;a = (LL)a * a % p;k >>= 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;scanf("%d", &n);while (n -- ){int a, b;scanf("%d%d", &a, &b);printf("%d\n", (LL)fact[a] * infact[b] % mod * infact[a - b] % mod); }//记住中间过程两个相乘就要% mod防止溢出!!!!【大数处理!】return 0;
}

大佬的逆元图

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

AcWing 887. 求组合数 III

给定 n 组询问,每组询问给定三个整数 a,b,p,其中 p 是质数,请你输出 CabmodpC^b_a ~mod~pCab​ mod p的值.

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一组 a,b,p。

输出格式
共 n 行,每行输出一个询问的解。

数据范围
1≤n≤20,
1≤b≤a≤101810^181018,
1≤p≤10510^5105,

输入样例:
3
5 3 7
3 1 5
6 4 13
输出样例:
3
3
2


Cab%pC_a^b \, \% \, pCab​%p (a, b为101810^{18}1018级别)

卢卡斯定理 Cab≡Ca%pb%pCa/pb/p(modp)\huge{\color{blue}{C_{a}^{b} \equiv C_{a\%p}^{b\%p} C_{a / p}^{b / p} }(mod \quad p)}Cab​≡Ca%pb%p​Ca/pb/p​(modp)

不想那么容易写错 - 全部long long就好了

#include <iostream>
#include <algorithm>using namespace std;typedef long long LL;int qmi(int a, int k, int 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 p)  //组合数二的公式的简写【没有存数组调用:则每次C[a][b] % p都要循环计算一遍】
{ if (b > a) return 0;int res = 1;  //c[a][b] = a! * infact(b!) * infact((a-b)!)   for (int i = 1, j = a; i <= b; i ++, j -- ) { //双指针写法 j 枚举 a! / (a-b)!, i枚举求infact(b!) 【i <= b停止使得j枚举从a->a-b:等效a! / (a - b)!】res = (LL)res * j % p; res = (LL)res * qmi(i, p - 2, p) % p;} //可简写res = res * j % p * qmi(i, p - 2, p) % p;return res;
}//公式可统一简写为f(参数)
int lucas(LL a, LL b, int p) //卢卡斯定理 : LL
{if (a < p && b < p) return C(a, b, p); //在(mod p)内直接求C[a][b] % preturn (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;cout << lucas(a, b, p) << endl;}return 0;
}

纯LL版

#include <iostream>
#include <algorithm>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;a = a * a % p;k >>= 1;}return res;
}LL C(LL a, LL b, LL p)
{LL res = 1;for(int i = 1, j = a; i <= b; i ++, j --)res = res * j % p * qmi(i, p - 2, p) % p;return res;
}LL lucas(LL a, LL b, LL p) //卢卡斯定理 : LL
{if (a < p && b < p) return C(a, b, p); //在(mod p)内直接求C[a][b] % preturn C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;  //【公式】
}int main()
{LL n;cin >> n;while (n -- ){LL a, b, p; scanf("%lld%lld%lld", &a, &b, &p);cout << lucas(a, b, p) << endl;}return 0;
}

AcWing 888. 求组合数 IV

输入 a,b,求 CabC^b_aCab​ 的值。

注意结果可能很大,需要使用高精度计算。

输入格式
共一行,包含两个整数 a 和 b。

输出格式
共一行,输出 CabC^b_aCab​ 的值。

数据范围
1≤b≤a≤5000
输入样例:
5 3
输出样例:
10


精准值CabC_a^bCab​ (a, b <= 5000)
双指针枚举即可 for(int i = 1, j = a; i <= b; i++, j--)【加高精度乘除法】
Cab=a!b!(a−b)!=a∗(a−1)......(a−b+2)∗(a−b+1)b!C_a^b = \frac{a!}{b!(a-b)!} = \frac{a * (a - 1) ......(a - b + 2) * (a - b + 1)}{b!}Cab​=b!(a−b)!a!​=b!a∗(a−1)......(a−b+2)∗(a−b+1)​

但是这样运行效率慢, 进一步优化:【分解质因数】

①分解质因数
x=p1α1p2α2p3α3...pnαn\color{lightblue}{x = p_1^{\alpha_1} p_2^{\alpha_2} p_3^{\alpha_3} ... p_{n}^{\alpha_n} }x=p1α1​​p2α2​​p3α3​​...pnαn​​

a!=⌊ap⌋+⌊ap2⌋+...+⌊apn⌋\color{blue}{a! = \lfloor \frac{a}{p} \rfloor + \lfloor \frac{a}{p^2} \rfloor + ... + \lfloor \frac{a}{p^n} \rfloor}a!=⌊pa​⌋+⌊p2a​⌋+...+⌊pna​⌋

分母取出的pip^ipi的个数 减去 分子取出的pip^ipi的个数 再相乘
即组合数=∏i(分母的pi个数−分子的pi个数)组合数 = \prod_{i}^{} ({分母的p_i个数 - 分子的p_i个数})组合数=∏i​(分母的pi​个数−分子的pi​个数)

不错的解释
代码:

①get_primes(a),CabC_a^bCab​, a >= b >= 所有质因数【线性筛法-质数打表】 (枚举到a即可,N也行但没必要)
②get(n, p)求每个质数在n中的次数
③vector<int> mul高精度乘法求$组合数 = \prod_{i}^{} ({分母的p_i个数 - 分子的p_i个数}) $
sum[i] = get(a, p) - get(a - b, p) - get(b, p) 【分母pi−分子pip_i - 分子p_ipi​−分子pi​ = CabC_a^bCab​中pip_ipi​的幂】

#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] <= n / i; j ++ ){st[primes[j] * i] = true;if (i % primes[j] == 0) break;}}
}int get(int n, int p)  //第一次把n中所有p的个数取出, 第二次n中把所有p^2的个数取出 ...
{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时才删除多余的0, b!=0不需要这行[佬的解释]return c; //加上也不影响hh
}int main()
{int a, b;cin >> a >> b;get_primes(a); //a, b质因数不超过a   (a >= b)for (int i = 0; i < cnt; i ++ ){int p = primes[i];sum[i] = get(a, p) - get(a - b, p) - get(b, p); //分母 - 分子 = 组合数中p_i的幂}vector<int> res;res.push_back(1); //乘积因子1for (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;
}

AcWing 889. 满足条件的01序列

给定 n 个 0 和 n 个 1,它们将按照某种顺序排成长度为 2n 的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中 0 的个数都不少于 1 的个数的序列有多少个。

输出的答案对 109+710^9 + 7109+7取模。

输入格式
共一行,包含整数 n。

输出格式
共一行,包含一个整数,表示答案。

数据范围
1≤n≤10510^5105
输入样例:
3
输出样例:
5


满足卡特兰数 Cat(n) = C2nnn+1\frac{C_{2n}^n}{n + 1}n+1C2nn​​
Cab(a,b在105级别)C_a^b(a,b在10^5级别)Cab​(a,b在105级别)
利用①组合数拆解公式 + ②阶乘预处理 + ③阶乘的逆元(费马小定理)
Cab=a!b!(a−b)!C_a^b = \frac{a!}{b!(a-b)!}Cab​=b!(a−b)!a!​
Cab=fact(a)∗infact(b)∗infact(a−b)\color{blue}{C_a^b = fact(a) * infact(b) * infact(a - b) }Cab​=fact(a)∗infact(b)∗infact(a−b)
【代码】

逆元简写版函数**C(a, b, mod)** , 求逆元快速幂**qmi(n + 1, mod - 2, mod)**
LL res = C(a, b, mod) * qmi(n + 1, mod - 2, mod) % mod;

封装版 - mycode2022【通用简记】

#include <iostream>
#include <algorithm>using namespace std;typedef long long LL;const LL mod = 1e9 + 7;LL qmi(LL a, LL k, LL p) //目前认为:函数传参会强转 【int实参传入LL型不会报错就是了】
{LL res = 1 % p;while(k){if(k & 1) res = res * a % p;a = a * a % p;k >>= 1;}return res;
}LL C(LL a, LL b, LL p)
{LL res = 1 % p;for(LL i = 1, j = a; i <= b; i++, j--){ //超简版 res = res * j % p * qmi(i, p - 2, p) % p;res = res * j % p;   res = res * qmi(i, p - 2, p) % p;}return res;
}int main()
{LL n;cin >> n;LL a = n * 2, b = n; //干脆全改LLLL res = C(a, b, mod) * qmi(n + 1, mod - 2, mod)  % mod; //发现函数LL, 也可以传int型参数cout << res << endl;return 0;
}

疑惑
res * (i, p - 2, p)结果 $ = 0$ 不会报错 [猜测,分割表达式,使用最后一个表达式的值, p % p == 0]
res * (i, p - 2) 结果 $ \neq 0$ 证实用 p - 2 % p , 即表达式的选用

原版 - 简写版

#include <iostream>
#include <algorithm>using namespace std;typedef long long LL;const int N = 100010, mod = 1e9 + 7;int qmi(int a, int k, int p)
{int res = 1;while (k){if (k & 1) res = (LL)res * a % p;a = (LL)a * a % p;k >>= 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;  //i的逆元     res = (LL)res * qmi(n + 1, mod - 2, mod) % mod;cout << res << endl;return 0;
}

AcWing 890. 能被整除的数

给定一个整数 n 和 m 个不同的质数 p1,p2,…,pm。

请你求出 1∼n 中能被 p1,p2,…,pm 中的至少一个数整除的整数有多少个。

输入格式
第一行包含整数 n 和 m。

第二行包含 m 个质数。

输出格式
输出一个整数,表示满足条件的整数的个数。

数据范围
1≤m≤16,
1≤n,pi≤10910^9109
输入样例:
10 2
2 3
输出样例:
7

AcWing 891. Nim游戏

给定 n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式
第一行包含整数 n。

第二行包含 n 个数字,其中第 i 个数字表示第 i 堆石子的数量。

输出格式
如果先手方必胜,则输出 Yes。

否则,输出 No。

数据范围
1≤n≤10510^5105,
1≤每堆石子数≤10910^9109
输入样例:
2
2 3
输出样例:
Yes


预习内容

NIM游戏给定N堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。我们把这种游戏称为NIM博弈。把游戏过程中面临的状态称为局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。NIM博弈不存在平局,只有先手必胜和先手必败两种情况。定理: NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ ... ^ An != 0公平组合游戏ICG若一个游戏满足:1. 由两名玩家交替行动;2. 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;3. 不能行动的玩家判负;则称该游戏为一个公平组合游戏。NIM博弈属于公平组合游戏,但城建的棋类游戏,比如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件2和条件3。有向图游戏给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。Mex运算设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即:mex(S) = min{x}, x属于自然数,且x不属于SSG函数在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, ..., yk,定义SG(x)为x的后继节点y1, y2, ..., yk 的SG函数值构成的集合再执行mex(S)运算的结果,即:SG(x) = mex({SG(y1), SG(y2), ..., SG(yk)})特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。有向图游戏的和设G1, G2, ..., Gm 是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步。G被称为有向图游戏G1, G2, ..., Gm的和。有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即:SG(G) = SG(G1) ^ SG(G2) ^ ... ^ SG(Gm)定理有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。

结论
n堆石子编号为a1,a2,a3...an−1,ana_1, a_2, a_3 ...a_{n-1}, a_na1​,a2​,a3​...an−1​,an​
两名玩家可以各自任选一堆石子, 其中一名先手拿石子, 另一名拿相同数量的石子
若a1⊕a2⊕a3...an−1⊕an=x≠0则先手胜a_1 \oplus a_2 \oplus a_3 ... a_{n-1} \oplus a_n =x \neq 0 \color{red}{则先手胜}a1​⊕a2​⊕a3​...an−1​⊕an​=x=0则先手胜 ,反之后手胜
证明看视频

#include <iostream>
#include <algorithm>using namespace std;const int N = 1e5 + 10;int main()
{int n;scanf("%d", &n);int res = 0;while (n -- ) {int x;scanf("%d", &x);res ^= x;}if (res) puts("Yes"); //不为0:先手胜else puts("No"); //防止后手胜return 0;
}

AcWing 892. 台阶-Nim游戏

现在,有一个 n 级台阶的楼梯,每级台阶上都有若干个石子,其中第 i 级台阶上有 ai 个石子(i≥1)。

两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。

已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式
第一行包含整数 n。

第二行包含 n 个整数,其中第 i 个整数表示第 i 级台阶上的石子数 ai。

输出格式
如果先手方必胜,则输出 Yes。

否则,输出 No。

数据范围
1≤n≤10510^5105,
1≤ai≤10910^9109
输入样例:
3
2 1 3
输出样例:
Yes

结论
n堆石子编号为a1,a2,a3...an−1,ana_1, a_2, a_3 ...a_{n-1}, a_na1​,a2​,a3​...an−1​,an​
两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿),最后无法进行操作的人视为失败。
若所有奇数级台阶异或不为0, 即 :
a1⊕a3⊕a5...an−1=x≠0则先手胜a_1 \oplus a_3 \oplus a_5... a_{n-1} =x \neq 0 \color{red}{则先手胜}a1​⊕a3​⊕a5​...an−1​=x=0则先手胜 ,反之后手胜

如y总举的例子:
(台阶按高度编号3->2->1) 【奇数阶为最后一步】
先拿石子让奇数台阶的3和1一直保持数量一致, 则到对手拿时遇到的3和1台阶一定是相同的,
则自己遇到的一定是不同的, 那么对手就会先遇到3与号都是0和0的情况, 此时先手胜利!

#include <iostream>
#include <algorithm>using namespace std;const int N = 100010;int main()
{int n;scanf("%d", &n);int res = 0;for (int i = 1; i <= n; i ++ ){int x;scanf("%d", &x);if (i & 1) res ^= x;}if (res) puts("Yes");else puts("No");return 0;
}

AcWing 893. 集合-Nim游戏

给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式
第一行包含整数 k,表示数字集合 S 中数字的个数。

第二行包含 k 个整数,其中第 i 个整数表示数字集合 S 中的第 i 个数 si。

第三行包含整数 n。

第四行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 hi。

输出格式
如果先手方必胜,则输出 Yes。

否则,输出 No。

数据范围
1≤n,k≤100,
1≤si,hi≤10000
输入样例:
2
2 5
3
2 4 7
输出样例:
Yes

AcWing 894. 拆分-Nim游戏

给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式
第一行包含整数 k,表示数字集合 S 中数字的个数。

第二行包含 k 个整数,其中第 i 个整数表示数字集合 S 中的第 i 个数 si。

第三行包含整数 n。

第四行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 hi。

输出格式
如果先手方必胜,则输出 Yes。

否则,输出 No。

数据范围
1≤n,k≤100,
1≤si,hi≤10000
输入样例:
2
2 5
3
2 4 7
输出样例:
Yes

动态规划

AcWing 2. 01背包问题

给定 n 堆石子以及一个由 k 个不同正整数构成的数字集合 S。

现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合 S,最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

输入格式
第一行包含整数 k,表示数字集合 S 中数字的个数。

第二行包含 k 个整数,其中第 i 个整数表示数字集合 S 中的第 i 个数 si。

第三行包含整数 n。

第四行包含 n 个整数,其中第 i 个整数表示第 i 堆石子的数量 hi。

输出格式
如果先手方必胜,则输出 Yes。

否则,输出 No。

数据范围
1≤n,k≤100,
1≤si,hi≤10000
输入样例:
2
2 5
3
2 4 7
输出样例:
Yes

背包串联:多重背包 拆分为 0-1背包

扩:谈钱购买时,消耗的体积就是物品的价值:v = w (还有其他变式)

[简写]:边输入边转移

#include <iostream>
#include<cstdio>//scanf && printf
#include<algorithm>//maxusing namespace std;const int N = 1010;
int n,m;
int v,w;//可边输入边判断, 也可以存下每种物品的体积v[i]和价值w[i]
int f[N];//f[j] :体积为j时的最大价值
int main()
{scanf("%d%d", &n, &m);for(int i = 0; i < n; i++){scanf("%d%d",&v, &w);for(int j = m; j >= v; j --)f[j] = max( f[j] , f[j - v] + w);}printf("%d", f[m]);return 0;
}

存物品体积v[N],价值w[N]

#include <iostream>
#include <algorithm>using namespace std;const int N = 1010;int n, m;
int v[N], w[N];
int f[N];int main()
{cin >> n >> m;for (int i = 1; i <= n; i ++ ) scanf("%d%d", &v[i], w[i]);for (int i = 1; i <= n; i ++ )for (int j = m; j >= v[i]; j -- )f[j] = max(f[j], f[j - v[i]] + w[i]);printf("%d", f[m]);return 0;
}

AcWing 3. 完全背包问题

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

最简写法:边读入边计算

#include<iostream>
#include<algorithm>using namespace std;const int N = 1010;int n, m;
int f[N];int main()
{scanf("%d%d", &n, &m);for(int i = 1; i <= n; i++){int v, w;scanf("%d%d", &v, &w);for(int j = v; j <= m; j++)f[j] = max(f[j], f[j - v] + w);}printf("%d", f[m]);return 0;
}

开数组先读入,再枚举计算

#include<isotream>using namespace std;const int N = 1010;
int n,m;
int v[N],w[N],f[N];int main()
{cin >> n >> m; //n种物品,容量m的背包for(int i = 1; i <= n; i++)scanf("%d%d", &v[i], &w[i]);for(int i = 1; i <= n; i++)for(int j = v[i]; j <= m; j++) //体积为j时选择放入的物品f[j] = max(f[j] , f[j - v[i]] + w[i]);     //【好记:与01背包不同按从小到大枚举体积:得max_w】cout << f[m] << endl;return 0;
}

二维朴素

const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][N];
int main()
{cin >> n >> m;for(int i = 1;i <= n;i++)scanf("%d%d",&v[i],&w[i]);for(int i = 1;i <= n;i++)for(int j = 0;j <= m;j++) //体积为j时选择放入的物品{f[i][j] = f[i-1][j]; //不能放时保存上一个值if(j >= v[i]) f[i][j] = max(f[i][j] , f[i][j - v[i]] + w[i]);  //能放时,比较最优解}cout << f[n][m] << endl;return 0;
}

观察f[][]

/*for(int i = 1;i <= n;i++)for(int j = 1;j <= m;j++) if(j != m)cout << f[i][j] << " ";else puts("");
*/

mycode2022

#include<iostream>using namespace std;const int N = 1010;int n, m;
int v[N], w[N];
int f[N];int main()
{scanf("%d%d", &n, &m);for(int i = 0 ; i < n; i++){scanf("%d%d", &v[i], &w[i]);for(int j = v[i]; j <= m; j++)f[j] = max(f[j], f[j - v[i]] + w[i]);}printf("%d", f[m]);return 0;
}

AcWing 4. 多重背包问题

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤100
0<vi,wi,si≤100
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

闫氏DP分析法一、状态表示:f[i][j]
1. 集合:从前i个物品中选,且总体积不超过j的所有方案的集合.
2. 属性:最大值二、状态计算:
1. 思想-----集合的划分
2. 集合划分依据:根据第i个物品有多少个来划分.含0个、含1个···含k个.
状态表示与完全背包朴素代码一样均为:
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);时间复杂度O(n∗v∗s)

最精简版-边读入边计算【思想:拆成0-1背包判断】

#include <iostream>using namespace std;const int N = 1010;int f[N];
int n, m;
int v, w, s;int main()
{scanf("%d%d", &n, &m);while(n--)//n之后没用,可以n-- , 也可for(int i = 1; i <= n; i++){scanf("%d%d%d", &v, &w, &s);for(int i = 1;i <= s; i++)//拆成s件相同价值的物品 --> s次0-1背包计算for(int j = m; j >= v ; j--)f[j] = max(f[j], f[j - v] + w);}printf("%d", f[m]);
}

朴素版

#include <iostream>
#include <algorithm>using namespace std;
const int N = 110;int v[N], w[N], s[N];
int f[N][N];
int n, m;int main(){cin >> n >> m;for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i] >> s[i];for(int i = 1; i <= n; i ++){//枚举种类for(int j = 1; j <= m; j ++){//枚举体积for(int k = 0; k <= s[i]; k ++){//枚举件数if(j >=  k * v[i]){//转移限制条件f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);//取k件物品减对应k件物品价值}}}}cout << f[n][m] << endl;return 0;
}

AcWing 5. 多重背包问题 II

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
提示:
本题考查多重背包的二进制优化方法。

输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10


二进制优化 + 0-1背包模板
思想举例:
7 的二进制111 :3位选不选0/1
1 2 4可组合成 0-7

但取 s = 10 ,非2^n - 1
就用 s - 1 - 2 - 4  = 3  < 8 ,已拆分的1 2 4对应二进制:1 10 100  【剩下的3单独加入集合】1 2 4 3 -- >枚举  (全不选) 0 - 10 (全选)   优化完能解决: 枚举v[i]物品体积 * s[i]每种物品个数 * m体积限制  :  4e10

不开数组版-边读入边计算[模板]

a, b, s : 每种物品: 体积v 价值w 数量s

#include<iostream>
#include<cstdio>using namespace std;const int N = 12010, M = 2010;//N:拆分后物品件数最多12000 , M : 背包体积int n, m;
int v[N], w[N]; //逐一枚举最大是N*logS
int f[M]; // 背包体积 < M , f[j]: 体积为j时的最大价值int main()
{scanf("%d%d", &n, &m); //n件种类组合,m总体积int cnt = 0; //记录拆分后种类 ,最后 n  = cntfor(int i = 1; i <= n; i++){int a, b, s;//【开局部变量的好处就是可以在不同作用域内有多个重名但不冲突】 scanf("%d%d%d", &a, &b, &s);  //不能用v,w和数组重名!!! int k = 1; //(乘积用1):拆分初始项1  ,k *= 2    1 2 4 (1 10 100)...while(k <= s){cnt ++;v[cnt] = a * k;   // 原来共 a * s  拆成 a * 1 + a * 2 + a * 4 ....  w[cnt] = b * k;s -= k;k *= 2;  //也可以 k <<= 1}if(s > 0)// 最后若非 2^n-1  , 取 s 与 (2^n-1) 的余数   ,如 10  ,1 2 4 ... 3  ,则最后一项取 3v,3w{cnt ++;v[cnt] = a * s;   w[cnt] = b * s;}}n = cnt;  //*每个拆分的背包占体积不同 -- 种类不同(变多) //拆项后转化成01背包一维优化for(int i = 1; i <= n ; i ++)for(int j = m ; j >= v[i]; j --)f[j] = max(f[j], f[j - v[i]] + w[i]);printf("%d", f[m]);return 0;
}

开数组版-存每种物品属性

a[N], b[N], s[N];每种物品: 体积v 价值w 数量s

#include<iostream>using namespace std;const int N = 12010, M = 2010;//N:拆分后物品件数最多12000 , M : 背包体积int n, m;
int v[N], w[N]; //逐一枚举最大是N*logS
int f[M]; // 背包体积 < M , f[j]: 体积为j时的最大价值
int a[N], b[N], s[N];//每种物品: 体积v 价值w 数量sint main()
{scanf("%d%d", &n, &m); //n件种类组合,m总体积int cnt = 0; //记录拆分后种类 ,最后 n  = cntfor(int i = 1;i <= n;i++) scanf("%d%d%d", &a[i], &b[i], &s[i]);  //不能用v,w和数组重名!!!for(int i = 1;i <= n;i++){int k = 1; //(乘积用1):拆分初始项1  ,k *= 2    1 2 4 (1 10 100)...while(k <= s[i]){cnt ++ ;v[cnt] = a[i] * k;   // 原来共 a * s  拆成 a * 1 + a * 2 + a * 4 ....  w[cnt] = b[i] * k;s[i] -= k;k *= 2;  //也可以 k <<= 1}if(s[i] > 0)// 最后若非 2^n-1  , 取 s 与 (2^n-1) 的余数   ,如 10  ,1 2 4 ... 3  ,则最后一项取 3v,3w{cnt ++ ;v[cnt] = a[i] * s[i];   w[cnt] = b[i] * s[i];}}n = cnt;  //*每个拆分的背包占体积不同 -- 种类不同(变多) //拆项后转化成01背包一维优化for(int i = 1;i <= n ;i ++)for(int j = m ;j >= v[i];j --)f[j] = max(f[j],f[j-v[i]] + w[i]);printf("%d", f[m]);return 0;
}

AcWing 9. 分组背包问题

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。

接下来有 N 组数据:

每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤100
0<Si≤100
0<vij,wij≤100
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8

f[j]:只从前i组物品中选, 且总体积不超过j的选法
不选:f[j] , 选:f[j - v[i][k]] + w[i][k] : 比较取max继续转移下一个状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZlhPqaZH-1676012522975)(https://cdn.acwing.com/media/article/image/2022/10/30/165952_ab4c6af857-分组背包.png)]

#include <iostream>
#include <algorithm>using namespace std;const int N = 110;int n, m;
int v[N][N], w[N][N], s[N];  //二维存入物品体积、价值 :v[i][k] (第i组第k件物品体积)
int f[N];  //f[j] : 只从前i组物品中选, 且总体积不超过j的选法int main()
{scanf("%d%d", &n, &m);//依题读入for (int i = 0; i < n; i ++ ){scanf("%d", &s[i]);//存每组物品数量for (int j = 0; j < s[i]; j ++ )//读入每组物品:下标从0开始scanf("%d%d", &v[i][j], &w[i][j]);}for (int i = 0; i < n; i ++ )for (int j = m; j >= 0; j -- )  //0-1背包模板 【注意写法:k在下面枚举,此时下标还无法表示:只能写j >= 0(且j与k枚举顺序不能变)】for (int k = 0; k < s[i]; k ++ )  //每组物品下标从0开始if (v[i][k] <= j)  //条件限制:能放的下此物品 v[i][k] : 第i组的第k件物品f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);   //选或不选printf("%d", f[m]);return 0;
}

AcWing 898. 数字三角形

给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

         73   88   1   02   7   4   44   5   2   6   5

输入格式
第一行包含整数 n,表示数字三角形的层数。

接下来 n 行,每行包含若干整数,其中第 i 行表示数字三角形第 i 层包含的整数。

输出格式
输出一个整数,表示最大的路径数字和。

数据范围
1≤n≤500,
−10000≤三角形中的整数≤10000
输入样例:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例:
30


一维优化最简版
这里提一嘴dp认知:(关键在如何合理划分能包括所有情况:积累思路)
dp集合划分就是通过不同的想法把数学排列组合且根据题目限制后的所有情况划分集合

思路:
路径从起点走到终点的距离 == 终点走回起点的距离
逆序从最后一行开始走a[n][i] -->a[1][1] ,只需要一维:最终max都转移到f[1] : 以第n行第i列走到[1,1]的最大值max
往上转移:转移到上层[i][j] , 则下层相对于上层的下标为[i + 1][j], [i + 1][j + 1]
i用循环等效对应, f[j]一维枚举计算[j]与[j+1]:最后一轮i = 1,最终 f[1] 存转移到 a[1][1] 的max

#include<iostream>
#include<cstdio>
#include<algorithm>using namespace std;const int N = 1010;int f[N];
int a[N][N];int main()
{int n;scanf("%d", &n);for(int i = 1; i <= n; i++)//读入三角形, 为了不判断边界:从1开始for(int j = 1; j <= i; j++)scanf("%d", &a[i][j]);for(int i = n; i >= 1; i--) //推导后的一维优化:i=n 逆序从最后一行开始, j正序 : 从最后一行走到顶【顶只有1个】for(int j = 1; j <= i; j++)f[j] = max(f[j], f[j + 1]) + a[i][j];//往上走:转移下标为j和j + 1printf("%d", f[1]);return 0;
}

标准版

#include <iostream>
#include <algorithm>using namespace std;const int N = 510, INF = 1e9;int n;
int a[N][N];//存三角形
int f[N][N];//f[n][i] : 到第n个点的路径之和maxint main()
{scanf("%d", &n);for (int i = 1; i <= n; i ++ ) //dp用到下标i - 1 : 从1开始for (int j = 1; j <= i; j ++ )scanf("%d", &a[i][j]);//i = 1更准确, 0当然也可以(初始-INF需包括三角形及其边界)for (int i = 1; i <= n; i ++ )//下三角形  //因为n∈[-1e4,1e4], 存在负数 , 所以应该将两边也设为-INFfor (int j = 0; j <= i + 1; j ++ )f[i][j] = -INF;//求最大, 初始负无穷:-INFf[1][1] = a[1][1];//下标不越界:初始起点需赋值for (int i = 2; i <= n; i ++ )//2开始for (int j = 1; j <= i; j ++ )f[i][j] = max(f[i - 1][j - 1], f[i - 1][j]) + a[i][j];//选取上一个状态大的 + 此状态值//等效f[i][j] = max(f[i - 1][j - 1] + a[i][j], f[i - 1][j] + a[i][j]);int res = -INF;//求最大, 初始负无穷:-INFfor (int i = 1; i <= n; i ++ ) res = max(res, f[n][i]); //最后一行都是最终状态:选取maxprintf("%d\n", res);return 0;
}

简化版【边读入边覆盖计算】

评价是比标准版好记一些hh

#include <iostream>using namespace std;const int N = 510, INF = 1e9;int n;
int a[N][N];int main()
{scanf("%d", &n);for(int i = 1;i <= n; i++)//想象三角形[i从1开始为1~n],[0 <= j <= i]外面一圈的坐标[i,0]  for (int j = 0; j <= i + 1; j++)//与每行最后一个为[i,i] :每行边界[i,i + 1]a[i][j] = -INF; for(int i = 1;i <= n; i++)//读入 【用到下标i - 1,为了不判断边界 :从1开始存入,但边界要负无穷 】for(int j = 1;j <= i; j++) {scanf("%d", &a[i][j]);//边读入边覆盖计算a[i][j] += max(a[i-1][j], a[i-1][j-1]);//从 左上 和 正上 转移}int res = -INF;for(int i = 1;i <= n; i++)res = max(res, a[n][i]); printf("%d" ,res);return 0;
}

倒序优化:从底部a[n][i]走到顶部a[1][1]

#include <iostream>using namespace std;const int N = 510, INF = 1e9;
int n;
int a[N][N];int main()
{scanf("%d", &n);for(int i = 1;i <= n; i++)//想象三角形[i从1开始为1~n],[0 <= j <= i]外面一圈的坐标[i,0]  for (int j = 1; j <= i; j++)//与每行最后一个为[i,i] :每行边界[i,i + 1]scanf("%d", &a[i][j]);for(int i = n; i >= 1; i --)//读入 【用到下标i - 1,为了不判断边界 :从1开始存入,但边界要负无穷 】for(int j = i;j >= 1; j--) a[i][j] += max(a[i+1][j], a[i+1][j+1]);//从底部往正上或者左上走printf("%d" ,a[1][1]);return 0;
}

递归-记忆化搜索-从下到上版

#include <iostream>
#include <cstring>
#include <algorithm>using namespace std;const int N = 510, INF = 1e9;int n;int a[N][N], dp[N][N];int f(int x, int y) //caculate递归
{if (dp[x][y] != -INF) return dp[x][y]; //有搜索过,剪枝,沿用上一次结果if (x == n) return a[x][y]; //边界,x == n放回当前格子值 int sum = max(f(x + 1, y), f(x + 1, y + 1)) + a[x][y]; //下面更大的 + 当前dp[x][y] = sum; //记忆化存入return dp[x][y];
}int main()
{cin >> n;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= i; j ++ )dp[i][j] = -INF;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= i; j ++ )cin >> a[i][j];cout << f(1, 1) << endl;return 0;
}

AcWing 895. 最长上升子序列

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式
第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

输出格式
输出一个整数,表示最大长度。

数据范围
1≤N≤1000,
−109≤数列中的数≤10910^9109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4

集合划分:f[i]为以a[i]结尾的最长上升子序列

#include <iostream>
#include <algorithm>using namespace std;const int N = 1010;int n;
int a[N];
int f[N];int main()
{scanf("%d", &n);for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);int res = 0;for (int i = 1; i <= n; i ++ ){f[i] = 1;//以i结尾的子序列:初始长度为本身 = 1for (int j = 1; j < i; j ++ )if (a[i] > a[j])//以i结尾子序列 = 以j结尾的子序列 + 1f[i] = max(f[i], f[j] + 1);res = max(res, f[i]);}printf("%d", res);return 0;
}

DP+二分

VictorWu

#include <iostream>using namespace std;const int N = 1010;
int n, cnt;
int w[N], f[N];int main() {cin >> n;for (int i = 0 ; i < n; i++) cin >> w[i];f[cnt++] = w[0]; //f[i]表示长度为i的最长上升子序列,末尾最小的数字for (int i = 1; i < n; i++) {if (w[i] > f[cnt-1]) f[cnt++] = w[i];else {int l = 0, r = cnt - 1; //二分法找第一个大于或等于w[i]的数字while (l < r) {int mid = (l + r) >> 1;if (f[mid] >= w[i]) r = mid;else l = mid + 1;}f[r] = w[i];}}cout << cnt << endl;return 0;
}

AcWing 896. 最长上升子序列 II

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式
第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

输出格式
输出一个整数,表示最大长度。

数据范围
1≤N≤100000,
−109≤数列中的数≤10910^9109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4

AcWing 897. 最长公共子序列

给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。

输入格式
第一行包含两个整数 N 和 M。

第二行包含一个长度为 N 的字符串,表示字符串 A。

第三行包含一个长度为 M 的字符串,表示字符串 B。

字符串均由小写字母构成。

输出格式
输出一个整数,表示最大长度。

数据范围
1≤N,M≤1000
输入样例:
4 5
acbd
abedc
输出样例:
3


大佬的图:


#include <iostream>
#include <algorithm>using namespace std;const int N = 1010;int n, m;
char a[N], b[N];
int f[N][N];int main()
{scanf("%d%d", &n, &m);scanf("%s%s", a + 1, b + 1);//转移方程用到下标1,从1开始for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ ){f[i][j] = max(f[i - 1][j], f[i][j - 1]);//不相等有一个指针向前移动,i或j, 选取转移后的maxif (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);//两个字符相等,双指针继续判断下一位//else f[i][j] = max(f[i - 1][j] , f[i][j - 1]); //按逻辑写这里 }printf("%d\n", f[n][m]);return 0;
}

AcWing 902. 最短编辑距离

给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:

删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。

输入格式
第一行包含整数 n,表示字符串 A 的长度。

第二行包含一个长度为 n 的字符串 A。

第三行包含整数 m,表示字符串 B 的长度。

第四行包含一个长度为 m 的字符串 B。

字符串中均只包含大小写字母。

输出格式
输出一个整数,表示最少操作次数。

数据范围
1≤n,m≤1000
输入样例:
10
AGTCTGACGC
11
AGTAAGTAGGC
输出样例:
4


题意: 每次操作可改变一个元素[增 删 改], 求使得a[]与b[]相等的最少操作次数
集合表示 :f[i][j] : a1......ai与b1......bj相等匹配a_1 ...... a_i 与 b_1 ...... b_j 相等匹配a1​......ai​与b1​......bj​相等匹配
状态划分 : 增 删 改
:a[i]与b[j]中,a的前i个与b的前j−1个匹配,a需增添一个与b[j]相等的元素,对应f[i−1][j]+1a[i]与b[j]中, a的前i个与b的前j-1个匹配,a需增添一个与b[j]相等的元素, 对应f[i - 1][j] + 1a[i]与b[j]中,a的前i个与b的前j−1个匹配,a需增添一个与b[j]相等的元素,对应f[i−1][j]+1
:$a[i]与b[j]中,a的前i-1个与b的前j个匹配,需删除a[i], 对应f[i][j - 1] + 1 $
:【分两种情况 加0加1
~ ① a[i]与b[j]中,a的前i−1个与b的前j−1个匹配,需修改a[i],使得a[i]==b[j],对应f[i−1][j−1]+1~a[i]与b[j]中,a的前i-1个与b的前j-1个匹配,需修改a[i],使得a[i] == b[j], 对应f[i-1][j-1] + 1 a[i]与b[j]中,a的前i−1个与b的前j−1个匹配,需修改a[i],使得a[i]==b[j],对应f[i−1][j−1]+1
~ ② a[i]与b[j]中,a的前i−1个与b的前j−1个匹配,且a[i]==b[j]则无需操作,对应f[i−1][j−1]+0~a[i]与b[j]中,a的前i-1个与b的前j-1个匹配,且a[i] == b[j] 则无需操作, 对应f[i-1][j-1] + 0 a[i]与b[j]中,a的前i−1个与b的前j−1个匹配,且a[i]==b[j]则无需操作,对应f[i−1][j−1]+0

边界:f[0][i]:a增加i次变成b,f[i][0]:a删除i次变成b~f[0][i]:a增加i次变成b, f[i][0]:a删除i次变成b f[0][i]:a增加i次变成b,f[i][0]:a删除i次变成b


【直接记代码hh】

#include <iostream>
#include <algorithm>using namespace std;const int N = 1010;int n, m; //strlen(a), strlen(b)
char a[N], b[N];
int f[N][N];int main()
{scanf("%d%s", &n, a + 1); //【转移方程涉及 i - 1为了省去边界判断, 最好初始化从1开始】scanf("%d%s", &m, b + 1); //[否则影响如求min碰到边界0则被边界更新为0,出错]//边界for (int i = 0; i <= m; i ++ ) f[0][i] = i; //a前0个字符与b的前i个字符匹配:需要添加i次for (int i = 0; i <= n; i ++ ) f[i][0] = i; //a前i个字符与b的前0个字符匹配:需要删除i次for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ ){f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1); //增, 删//if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]); //else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1); f[i][j] = min(f[i][j], f[i - 1][j - 1] + (a[i] != b[j]) ); //改的两种情况简写:+(表达式返回值)}printf("%d\n", f[n][m]); //把a的前n个字母变成b的前m个字母return 0;
}
/*
(上述按y总理解, 实际理解有偏差(如仅一个不同但并非最后一个),通俗理解为操作步骤 )
[**my理解为匹配个数**] [另一种通俗的理解方式](https://www.acwing.com/user/myspace/index/91657/)
在回过头来看:可以中间匹配过程遇到不同就修改:即y总正确!!!
*/

AcWing 899. 编辑距离

给定 n 个长度不超过 10 的字符串以及 m 次询问,每次询问给出一个字符串和一个操作次数上限。

对于每次询问,请你求出给定的 n 个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。

每个对字符串进行的单个字符的插入、删除或替换算作一次操作。

输入格式
第一行包含两个整数 n 和 m。

接下来 n 行,每行包含一个字符串,表示给定的字符串。

再接下来 m 行,每行包含一个字符串和一个整数,表示一次询问。

字符串中只包含小写字母,且长度均不超过 10。

输出格式
输出共 m 行,每行输出一个整数作为结果,表示一次询问中满足条件的字符串个数。

数据范围
1≤n,m≤1000,

输入样例:
3 2
abc
acd
bcd
ab 1
acbd 2
输出样例:
1
3


多次求最短编辑距离【封装】
思路: 求出最短编辑距离 判断 是否不超过上限limit :
if(编辑距离<=limit)res++;~if(编辑距离 <= limit) res ++; if(编辑距离<=limit)res++;

#include <iostream>
#include <algorithm>
#include <string.h>using namespace std;const int N = 15, M = 1010;int n, m;
int f[N][N];
char str[M][N];int edit_distance(char a[], char b[]) //求a[] 变成 b[] 的最少操作次数
{int la = strlen(a + 1), lb = strlen(b + 1); //注意首地址从1开始!!!for (int i = 0; i <= lb; i ++ ) f[0][i] = i;for (int i = 0; i <= la; i ++ ) f[i][0] = i;for (int i = 1; i <= la; i ++ )for (int j = 1; j <= lb; j ++ ){f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1); //增, 删f[i][j] = min(f[i][j], f[i - 1][j - 1] + (a[i] != b[j])); //改的两种情况-精妙简写}return f[la][lb];
}int main()
{scanf("%d%d", &n, &m);for (int i = 0; i < n; i ++ ) scanf("%s", str[i] + 1); //每行从1开始 存入awhile (m -- ) //每轮询问:【str[][]中有多少个a满足操作次数 <= limit变成b】{char s[N];int limit;scanf("%s%d", s + 1, &limit); //读入b 和 操作次数限制limit  int res = 0;  for (int i = 0; i < n; i ++ )if (edit_distance(str[i], s) <= limit) //【注意传入从0开始!!对应函数内部才为从1开始取长度】res ++ ;printf("%d\n", res);}return 0;
}

AcWing 282. 石子合并

设有 N 堆石子排成一排,其编号为 1,2,3,…,N。

每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。

例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1,2 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24;

如果第二步是先合并 2,3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

输入格式
第一行一个数 N 表示石子的堆数 N。

第二行 N 个数,表示每堆石子的质量(均不超过 1000)。

输出格式
输出一个整数,表示最小代价。

数据范围
1≤N≤300
输入样例:
4
1 3 5 2
输出样例:
22

限制:每次只能合并相邻的两堆 ==> 根据限制划分:
第k堆为最后一次合并: f[l][k]+f[k+1][r]+s[r]−s[l−1];f[l][k] + f[k + 1][r] + s[r] - s[l - 1];f[l][k]+f[k+1][r]+s[r]−s[l−1];(区间和:代价用前缀和计算)


#include <iostream>
#include <algorithm>using namespace std;const int N = 310;int n;
int s[N];
int f[N][N];int main()
{scanf("%d", &n);for (int i = 1; i <= n; i ++ )//读入 , 初始化前缀和{scanf("%d", &s[i]);s[i] += s[i - 1];}//【区间枚举】for (int len = 2; len <= n; len ++ )//枚举合并的区间长度 【区间长度是1则不需要代价, 直接从2开始枚举】for (int i = 1; i + len - 1 <= n; i ++ )//枚举起点, 最后一个位置 <= n()不越界{int l = i, r = i + len - 1;f[l][r] = 1e8;//求最小, 初始需超过max边界值 (未初始化则全局为0,算出全为0...)for (int k = l; k < r; k ++ )f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);}//先不管最后一次合并的代价, 则代价转移为k点printf("%d\n", f[1][n]);return 0;
}

AcWing 900. 整数划分

一个正整数 n 可以表示成若干个正整数之和,形如:n=n1+n2+…+nkn=n_1+n_2+…+n_kn=n1​+n2​+…+nk​,其中 n1≥n2≥…≥nk,k≥1n_1≥n_2≥…≥n_k,~k≥1n1​≥n2​≥…≥nk​, k≥1。

我们将这样的一种表示称为正整数 n 的一种划分。

现在给定一个正整数 n,请你求出 n 共有多少种不同的划分方法。

输入格式
共一行,包含一个整数 n。

输出格式
共一行,包含一个整数,表示总划分数量。

由于答案可能很大,输出结果请对 109+710^9+7109+7 取模。

数据范围
1≤n≤1000
输入样例:
5
输出样例:
7

y总
佬の思路:
把1,2,3, … n分别看做n个物体的体积,问恰好能装满总体积为n的背包的总方案数 (枚举这n个物体均无数量限制,用完全背包模板)

①完全背包解法
状态表示:
f[i][j]表示只从1~i中选,且总和等于j的方案数

状态转移方程:
f[i][j] = f[i - 1][j] + f[i][j - i];
同完全背包可以一维优化(去掉i):
f[j] = f[j] + f[j - i]

#include <iostream>
#include <algorithm>using namespace std;const int N = 1010, mod = 1e9 + 7;int n;
int f[N];int main()
{cin >> n;f[0] = 1;for (int i = 1; i <= n; i ++ )for (int j = i; j <= n; j ++ )//从i开始正序f[j] = (f[j] + f[j - i]) % mod;  //【不懂推就记住】可以由上一个状态等价转移cout << f[n] << endl;return 0;
}

②其他算法
状态表示:
f[i][j]表示总和为i,总个数为j的方案数

状态转移方程:
f[i][j] = f[i - 1][j - 1] + f[i - j][j];

#include <iostream>
#include <algorithm>using namespace std;const int N = 1010, mod = 1e9 + 7;int n;
int f[N][N];int main()
{cin >> n;f[1][1] = 1;for (int i = 2; i <= n; i ++ )for (int j = 1; j <= i; j ++ )f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;int res = 0;for (int i = 1; i <= n; i ++ ) res = (res + f[n][i]) % mod;cout << res << endl;return 0;
}

AcWing 338. 计数问题

给定两个整数 a 和 b,求 a 和 b 之间的所有数字中 0∼9 的出现次数。

例如,a=1024,b=1032,则 a 和 b 之间共有 9 个数如下:

1024 1025 1026 1027 1028 1029 1030 1031 1032

其中 0 出现 10 次,1 出现 10 次,2 出现 7 次,3 出现 3 次等等…

输入格式
输入包含多组测试数据。

每组测试数据占一行,包含两个整数 a 和 b。

当读入一行为 0 0 时,表示输入终止,且该行不作处理。

输出格式
每组数据输出一个结果,每个结果占一行。

每个结果包含十个用空格隔开的数字,第一个数字表示 0 出现的次数,第二个数字表示 1 出现的次数,以此类推。

数据范围
0<a,b<100000000
输入样例:
1 10
44 497
346 542
1199 1748
1496 1403
1004 503
1714 190
1317 854
1976 494
1001 1960
0 0
输出样例:
1 2 1 1 1 1 1 1 1 1
85 185 185 185 190 96 96 96 95 93
40 40 40 93 136 82 40 40 40 40
115 666 215 215 214 205 205 154 105 106
16 113 19 20 114 20 20 19 19 16
107 105 100 101 101 197 200 200 200 200
413 1133 503 503 503 502 502 417 402 412
196 512 186 104 87 93 97 97 142 196
398 1375 398 398 405 499 499 495 488 471
294 1256 296 296 296 296 287 286 286 247

AcWing 291. 蒙德里安的梦想

求把 N×M 的棋盘分割成若干个 1×2 的长方形,有多少种方案。

例如当 N=2,M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。

如下图所示:

输入格式
输入包含多组测试用例。

每组测试用例占一行,包含两个整数 N 和 M。

当输入用例 N=0,M=0 时,表示输入终止,且该用例无需处理。

输出格式
每个测试用例输出一个结果,每个结果占一行。

数据范围
1≤N,M≤11
输入样例:
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
0 0
输出样例:
1
0
1
2
3
5
144
51205

能从k转移到j(即能从前一个状态转移过来):需满足
(j & k) == 0 没有冲突:横着放占两格:相邻状态转移不能为同一行
j | k 不存在连续奇数个0, 剩下的0能够放下竖着的两格才能填满

朴素写法,1000ms

#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;const int N = 12, M = 1 << N;int n, m;
long long f[N][M]; //注意枚举种类很多开LL
bool st[M]; //当前这一列所有的空着的【连续的0是否都为偶数:是否合法】int main() //核心:枚举横着放, 再空位都放竖的(代码枚举横放)
{while (cin >> n >> m, n || m) //n, m都为0停止{for (int i = 0; i < 1 << n; i ++ ) // i < 2^n   枚举所有方案:每行格子选或不选 0或1{int cnt = 0;//统计当前这一段连续0的个数【0为空着的位置】st[i] = true;//假设此方案可行truefor (int j = 0; j < n; j ++ ) //每轮i共选n位(二进制数), 取i的二进制每位判断选或不选 if (i >> j & 1) //i的此位选择1:选择放横的判断i的二进制的j - 1位是否为1{if (cnt & 1) st[i] = false;  //前面有连续的奇数个0, 不能成功转移, 此状态falsecnt = 0; //重新计数}else cnt ++ ; //不选为0  if (cnt & 1) st[i] = false; //最后一段连续个0,若是奇数个0,状态转移失败:【剩下的位置放竖的,必须剩余偶数个0】         }memset(f, 0, sizeof f); //f每轮需要重新置0【多轮输入】f[0][0] = 1; //不可能f[-1][0]转移过来, 空集也代表一种方案【不用填:变相填满】for (int i = 1; i <= m; i ++ ) //枚举所有列for (int j = 0; j < 1 << n; j ++ ) //枚举此列所有选或不选状态for (int k = 0; k < 1 << n; k ++ ) //枚举前一列的所有二进制排列状态if ((j & k) == 0 && st[j | k]) //j为转移后的状态, k为转移前的状态f[i][j] += f[i - 1][k]; //加上之前能转移的方案数cout << f[m][0] << endl; //能转移到m列,且f[m][0]不会"捅"出来, 才是一个合法方案}return 0;
}

去除无效状态的优化写法,230ms

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>using namespace std;typedef long long LL;const int N = 12, M = 1 << N; //枚举所有状态:2^n 【二进制位选或不选】int n, m;
LL f[N][M]; //种类很多 LL
vector<int> state[M]; //存能转移到state[]的状态(存前一个状态)
bool st[M]; //st预处理无效状态 : 所有二进制选择方案的状态:是否合法int main()
{while (cin >> n >> m, n || m) //n, m都为0停止{for (int i = 0; i < 1 << n; i ++ ){int cnt = 0; //统计当前方案每一段连续0的个数【0为空着的位置】bool is_valid = true; //此方案是否有效for (int j = 0; j < n; j ++ ) //每轮i共选n位(二进制数), 取i的二进制每位判断选或不选 if (i >> j & 1) //i的二进制的第j - 1位是否为1       i & 1 个位{if (cnt & 1) //连续的0为奇数个{is_valid = false; //方案无效break; }// cnt = 0;  y总写了, 但是my发现这肯定不会执行}else cnt ++ ;if (cnt & 1) is_valid = false;st[i] = is_valid; //st默认初始化为false :只有is_valid 为true才会标记为合法转移方案}for (int i = 0; i < 1 << n; i ++ )  //减少时间复杂度【预处理所有二进制方案的state状态】{state[i].clear(); //【多轮输入:注意清空】for (int j = 0; j < 1 << n; j ++ )if ((i & j) == 0 && st[i | j]) //符合转移规则state[i].push_back(j); // j为前一个状态能转移到i   }memset(f, 0, sizeof f);f[0][0] = 1;//空集也代表一种方案【不用填:变相填满】for (int i = 1; i <= m; i ++ ) //枚举for (int j = 0; j < 1 << n; j ++ ) //枚举此列所有选与不选状态for (auto k : state[j]) //枚举前一列的二进制排列所有状态【已经预处理状态,累加计算合法状态就行】f[i][j] += f[i - 1][k]; //加上前一个状态i-1的方案数      k = state[j] :表示k转移到jcout << f[m][0] << endl; //能转移到m列,且f[m][0]不会"捅"出来, 才是一个合法方案}return 0;
}
// 理解有困难的同学可以先看看这个稍微暴力一点的代码// #include <bits/stdc++.h>
// using namespace std;
// typedef long long LL;
// const int N = 12 , M = 1 << N;
// int n,m;
// LL f[N][M];
// bool st[M];
// int main()
// {//     while(cin>>n>>m,n)
//     {//         memset(f,0,sizeof f);
//         for(int i=0;i<1<<n;++i)
//         {//             int cnt=0;
//             bool ok=true;
//             for(int j=0;j<n;++j)
//                 if(i>>j&1)
//                 {//                     if(cnt&1)ok=false;
//                     cnt=0;
//                 }
//                 else cnt++;
//             if(cnt&1)ok=false;
//             st[i]=ok;
//         }
//         f[0][0]=1;
//         for(int i=1;i<=m;++i)
//             for(int j=0;j<1<<n;++j)
//                 for(int k=0;k<1<<n;++k)
//                     if(!(j&k)&&st[j|k])f[i][j]+=f[i-1][k];
//         cout<<f[m][0]<<"\n";
//     }
//     return 0;
// }

AcWing 91. 最短Hamilton路径

给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。

输入格式
第一行输入整数 n。

接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 i 到 j 的距离(记为 a[i,j])。

对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。

输出格式
输出一个整数,表示最短 Hamilton 路径的长度。

数据范围
1≤n≤20
0≤a[i,j]≤10710^7107
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18

[最短Hamilton路径] :等效旅行商问题:不重不漏地经过每个点恰好一次(每个点都有过路费)

状态表示:二进制枚举走或不走
f[i][j] : (二进制表示i的走法)经过i, 倒数第二个点为j的集合

起点和终点固定,中间过程考虑:
1.哪些点被用过
2.目前到哪个点

#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;const int N = 20, M = 1 << N;int n;
int w[N][N];//费用 【邻接矩阵】
int f[M][N];//二进制表示的第i选法 到达第j个点的最小费用int main()
{cin >> n; for (int i = 0; i < n; i ++ )for (int j = 0; j < n; j ++ )cin >> w[i][j];//相当于有向图【邻接矩阵】memset(f, 0x3f, sizeof f); //求min, 初始化INFf[1][0] = 0; //起点费用0【边界】 for (int i = 0; i < 1 << n; i ++ ) //枚举所有选法for (int j = 0; j < n; j ++ ) //枚举倒数第二个点if (i >> j & 1) 能走到j才有意义!!!【否则剪枝】for (int k = 0; k < n; k ++ ) //过程枚举:起点0-k的最短距离if(i - (1 << j) >> k & 1) //【最后到j,则从起点走到点k的路径不能经过点j】(取0-k中不经过j的状态:减去第j位的1)  f[i][j] = min(f[i][j] , f[i - (1 << j)][k] + w[k][j]); //取0-k中不经过j的状态更新//k视为倒数第二个点 [0 --> k+ k --> j] 费用:f[状态][k] + w[k][j]//'+',-'等算术优先级大于 ">>","<<"等位运算符【不清楚时按逻辑加小括号就行】cout << f[(1 << n) - 1][n - 1]; //最终状态[遍历完所有情况(0~2^n-1)][落到终点]return 0;
}
//【最后到j,则从起点走到点k的路径不能经过点j】(取0-k中不经过j的状态:减去第j位的1)
//但写if(i >> k & 1) 也可以 (具体分析可能有偏差)   分两种讨论 k == j, w[j][j] = 0 , k != j不影响
if(i - (1 << j) >> k & 1) //这样写逻辑是对的 —— 嗯, 就这样f[i][j] = min(f[i][j] , f[i - (1 << j)][k] + w[k][j]); //取0-k中不经过j的状态更新

AcWing 285. 没有上司的舞会

给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。

输入格式
第一行输入整数 n。

接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 i 到 j 的距离(记为 a[i,j])。

对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。

输出格式
输出一个整数,表示最短 Hamilton 路径的长度。

数据范围
1≤n≤20
0≤a[i,j]≤10710^7107
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18


经典树形DP - 类似状态机+ 邻接矩阵存边
题意

高兴度:不与直接上司匹配 ==> 若选取匹配的两点没有直接连线,加两点的高兴度
==> 即非父子节点【选取没有边相连的两点,加上点(员工)的高兴度】

集合划分 : 选根 / 不选根
状态:u根(当前) s子节点 转移条件:1表示选, 0表示不选
f[u][0] += max(f[s][1], f[s][1]); **不选根 **: 可以选子节点(选或不选都行
f[u][1] += f[s][0]; 选根 :不能选子节点

关键词:最大独立集问题(还没学过)

#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;const int N = 6010;int n;
int h[N], e[N], ne[N], idx;
int happy[N]; //高兴度  : 可以简写为w[]
int f[N][2];
bool has_fa[N]; //【判断当前节点是否有父节点!(找根,没有父节点则为根)】 (has_father) flagvoid add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
//树形DP
void dfs(int u)
{f[u][1] = happy[u];  //选择当前节点u, 加上自身的高兴度 [过程初始化]for (int i = h[u]; ~i; i = ne[i]) //遍历i的出边 i -> e[i] {int j = e[i]; //对应邻接点 dfs(j); //邻接点先全部初始化,加上自身高兴度//状态机f[u][1] += f[j][0]; f[u][0] += max(f[j][0], f[j][1]);}
}int main()
{scanf("%d", &n);for (int i = 1; i <= n; i ++ ) scanf("%d", &happy[i]); //高兴度 - 类比w[]权值memset(h, -1, sizeof h); //头结点初始化!for (int i = 0; i < n - 1; i ++ ) //读入n-1条有向边{int a, b;scanf("%d%d", &a, &b);add(b, a); has_fa[a] = true; //b -> a : a有父节点b(直接上司)}int root = 1;//找根节点【根节点没有父节点】while (has_fa[root]) root ++ ;  dfs(root); //根节点为起点 : =>输入起点根root 【状态机dfs-树形DP】 =>返回结果printf("%d\n", max(f[root][0], f[root][1])); //集合划分两种:ans = max(选根的高兴度和, 不选根的高兴度和)return 0;
}

AcWing 901. 滑雪

给定一张 n 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 0 到 n−1 不重不漏地经过每个点恰好一次。

输入格式
第一行输入整数 n。

接下来 n 行每行 n 个整数,其中第 i 行第 j 个整数表示点 i 到 j 的距离(记为 a[i,j])。

对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]≥a[x,z]。

输出格式
输出一个整数,表示最短 Hamilton 路径的长度。

数据范围
1≤n≤20
0≤a[i,j]≤10710^7107
输入样例:
5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0
输出样例:
18


记忆化搜索\red{记忆化搜索}记忆化搜索
题意:起点选取任意一个格子, 只能选取值更小的格子移动, 求最长滑动距离
此题不存在环路(限制往更小值滑动)

集合划分:四个方向能遍历的最远距离 (起点算1个长度)
属性:MAX, 初始-1

#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;const int N = 310;int n, m;
int g[N][N];
int f[N][N];int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};int dp(int x, int y)
{int &v = f[x][y]; //引用:简写代码 (取值) 判断是否走过if (v != -1) return v; //【记忆化搜索,已经遍历走过值确定,剪枝】v = 1; //最小值是1【起点】for (int i = 0; i < 4; i ++ ) {int a = x + dx[i], b = y + dy[i]; //向量宽搜if (a >= 1 && a <= n && b >= 1 && b <= m && g[x][y] > g[a][b]) //不超过边界 && 滑向高度更小v = max(v, dp(a, b) + 1); }return v;
}int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; i ++ ) //用到坐标 (i - 1, j), (i, j - 1), (i + 1, j), (i, j + 1)for (int j = 1; j <= m; j ++ )scanf("%d", &g[i][j]);memset(f, -1, sizeof f); //初始化f[x][y] = -1表示未走过int res = 0;for (int i = 1; i <= n; i ++ ) //遍历起点for (int j = 1; j <= m; j ++ )res = max(res, dp(i, j)); //返回状态printf("%d\n", res);return 0;
}

贪心

AcWing 905. 区间选点

给定 N 个闭区间 [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。

输出选择的点的最小数量。

位于区间端点上的点也算作区间内。

输入格式
第一行包含整数 N,表示区间数。

接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。

输出格式
输出一个整数,表示所需的点的最小数量。

数据范围
1≤N≤10510^5105,
−109≤ai≤bi≤10910^9109
输入样例:
3
-1 1
2 4
3 5
输出样例:
2

贪心证明(常用):
调整法 设 x > y , f(x) > f(y) 。。。【类似反证法】
夹逼准则 ans >= cnt && ans <= cnt <==> 答案ans == 贪心统计cnt
数学归纳法 在假设前k种情况符合, 证明第k+1种情况符合推导的等式

此题贪心思路: 按右端点排序, 若相交则可被同一个点选择,则不用加新的点,
不重叠则需多加一个点, 同时更新右边界ed = 更新区间的r, 下一个区间l与ed判是否相交
(注意此题是相交区间跳过不做任何操作,不更新边界 ≠\neq= 区间合并模板)

贪心只看眼前

右端点排序-区间筛选

#include <iostream>
#include <algorithm>using namespace std;const int N = 100010;int n;
struct Range
{int l, r;bool operator< (const Range &W)const{return r < W.r;}
}range[N];int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++ ) scanf("%d%d", &range[i].l, &range[i].r);sort(range, range + n);int res = 0, ed = -2e9;for (int i = 0; i < n; i ++ ) if (range[i].l > ed) //相交直接跳过, 没有交集则需选择新的点, 更新右边界r{res ++ ;ed = range[i].r;}printf("%d\n", res);return 0;
}

按照左端点排序

#include<iostream>
#include<algorithm>using namespace std;const int N=1e5+10;struct Range
{int  l,r;bool operator < (const Range &W)const{return l<W.l;}
}range[N];int main()
{int n;cin>>n;for(int i=0;i<n;i++){int l,r;scanf("%d%d",&l,&r);range[i]={l,r};}sort(range,range+n);int res=0,st=-2e9;for(int i=0;i<n;i++){if(st<range[i].l){res++;st=range[i].r;}else{st=min(st,range[i].r);}}printf("%d\n",res);return 0;
}

AcWing 908. 最大不相交区间数量

给定 N 个闭区间 [ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。

输出可选取区间的最大数量。

输入格式
第一行包含整数 N,表示区间数。

接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。

输出格式
输出一个整数,表示可选取区间的最大数量。

数据范围
1≤N≤10510^5105,
−10910^9109≤ai≤bi≤10910^9109
输入样例:
3
-1 1
2 4
3 5
输出样例:
2

和上一题代码一样-思路证明为关键部分

按右端点排序-PII版1

#include <iostream>
#include <cstring>
#include <algorithm>#define l first  //正常理解{l, r}
#define r secondusing namespace std;typedef pair<int, int> PII;const int N = 1e5 + 10;PII q[N];bool cmp(PII a, PII b) //按右端点排序
{return a.r < b.r;
}int main()
{int n;scanf("%d", &n);for(int i = 0; i < n; i++) scanf("%d%d", &q[i].l, &q[i].r); sort(q, q + n, cmp); //为了防止逻辑混乱 - 可以直接cmp比较函数int res = 0, ed = -2e9;for(int i = 0; i < n; i++)if(q[i].l > ed){res ++;ed = q[i].r;}printf("%d\n", res);    return 0;
}

按右端点排序-PII版2 (易错版)

#include <iostream>
#include <cstring>
#include <algorithm>#define r first  //让右端点排序 [pair有默认first排序, 不用写cmp函数, 但个人认为容易写乱]
#define l secondusing namespace std;typedef pair<int, int> PII;const int N = 1e5 + 10;PII q[N];int main()
{int n;scanf("%d", &n);for(int i = 0; i < n; i++) scanf("%d%d", &q[i].l, &q[i].r); //注意定义r为first【但按意思读入】sort(q, q + n); int res = 0, ed = -2e9;for(int i = 0; i < n; i++)if(q[i].l > ed){res ++;ed = q[i].r;}printf("%d\n", res);    return 0;
}

AcWing 906. 区间分组

给定 N 个闭区间 [ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。

输出最小组数。

输入格式
第一行包含整数 N,表示区间数。

接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。

输出格式
输出一个整数,表示最小组数。

数据范围
1≤N≤10510^5105,
−10910^9109≤ai≤bi≤10910^9109
输入样例:
3
-1 1
2 4
3 5
输出样例:
2


小根堆【优先队列-小根堆】维护所有组的右端点的最大值 (一个右端点代表一组)
利用小根堆性质:所有组中最小的右端点会在堆顶heap.top()

if(不可接)① 若所有组中最小的右端点 heap.top()** >= 当前区间左端点range[i].l ,
即当前区间左端点l无法接在已有的组的后面【
严格单调递增】, 此时只能再开一组**
(或** 队列为空创建第一组**) if (heap.empth() || heap.top() >= range[i].l) heap.range[i].r;
else(可接)② 当前枚举区间的左端点比所有组的最小的右端点要大【接在右端点最小组后面,则右端点被当前区间的r,需将之前右端点出队即pop()堆顶】
heap.pop(); heap.push(range[i].r);

类似DP题的拦截导弹第二问 LIS分组 , 还类似111.畜栏预定 : 求最少的分组
证明:答案ans >= 贪心cnt && 答案ans <= 贪心cnt , 即ans == cnt ,最优解 == 贪心解,可行

#include <iostream>
#include <algorithm>
#include <queue>using namespace std;const int N = 100010;int n;
struct Range
{int l, r;bool operator< (const Range &t)const{return l < t.l;}
}range[N];int main()
{scanf("%d", &n); for (int i = 0; i < n; i ++ ){int l, r;scanf("%d%d", &l, &r);range[i] = {l, r};}sort(range, range + n);//左端点排序从小到大priority_queue<int, vector<int>, greater<int>> heap;for (int i = 0; i < n; i ++ ){auto r = range[i];if (heap.empty() || heap.top() >= r.l) heap.push(r.r); //新创一组  else{heap.pop();heap.push(r.r);}}printf("%d\n", heap.size()); //组的数量 == 元素个数(存每组元素的[最大]右端点)return 0;
}

AcWing 907. 区间覆盖

给定 N 个闭区间 [ai,bi] 以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。

输出最少区间数,如果无法完全覆盖则输出 −1。

输入格式
第一行包含两个整数 s 和 t,表示给定线段区间的两个端点。

第二行包含整数 N,表示给定区间数。

接下来 N 行,每行包含两个整数 ai,bi,表示一个区间的两个端点。

输出格式
输出一个整数,表示所需最少区间数。

如果无解,则输出 −1。

数据范围
1≤N≤10510^5105,
−10910^9109≤ai≤bi≤10910^9109,
−10910^9109≤s≤t≤10910^9109
输入样例:
1 5
3
-1 3
2 4
3 5
输出样例:
2



证明略orz

从小到大按左端点排序
一个区间接着一个,(双指针)每次选取一个能连续衔接[下一个区间l <= 当前区间r = st(不断更新st) ]
选择其中右端点最大的

此轮更新的最大右端点r < 上一轮区间右端点st 则无法覆盖,依题意返回res = -1
如果当前r >= 需覆盖区间的右端点ed 则为成功覆盖标记success = true, break输出选取的区间个数res

#include <iostream>
#include <algorithm>using namespace std;const int N = 100010;int n;
struct Range
{int l, r;bool operator< (const Range &W)const{return l < W.l;}
}range[N];int main()
{int st, ed;scanf("%d%d", &st, &ed);scanf("%d", &n);for (int i = 0; i < n; i ++ ){int l, r;scanf("%d%d", &l, &r);range[i] = {l, r};}sort(range, range + n);int res = 0;bool success = false;for (int i = 0; i < n; i ++ ) // 【此题核心代码】 //{   //注意r每次-2e9开始, 保证更新int j = i, r = -2e9; //找到下一个区间左端点l衔接上一个区间r(l <= st, st更新)最大的右端点while (j < n && range[j].l <= st)  //起始左端点选取包含左边界st的区间(l在st的左边:l < st){r = max(r, range[j].r); //取二者右端点的最大值, 使得下一个更新的区间{左<包含当前右端点, 右端点尽可能大}j ++ ;}if (r < st) //r < st 【找不到衔接的区间【中间有断开】】循环所有区间没有找到能覆盖的r {res = -1;break;}res ++ ;if (r >= ed) //完全覆盖  [最后选取的右端点 >= 需覆盖的右边界ed]{success = true;break;}st = r;i = j - 1;}if (!success) res = -1;printf("%d\n", res);return 0;
}

AcWing 148. 合并果子

在一个果园里,达达已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。

达达决定把所有的果子合成一堆。

每一次合并,达达可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。

可以看出,所有的果子经过 n−1 次合并之后,就只剩下一堆了。

达达在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以达达在合并果子时要尽可能地节省体力。

假定每个果子重量都为 1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使达达耗费的体力最少,并输出这个最小的体力耗费值。

例如有 3 种果子,数目依次为 1,2,9。

可以先将 1、2 堆合并,新堆数目为 3,耗费体力为 3。

接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12,耗费体力为 12。

所以达达总共耗费体力=3+12=15。

可以证明 15 为最小的体力耗费值。

输入格式
输入包括两行,第一行是一个整数 n,表示果子的种类数。

第二行包含 n 个整数,用空格分隔,第 i 个整数 ai 是第 i 种果子的数目。

输出格式
输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。

输入数据保证这个值小于 231。

数据范围
1≤n≤10000,
1≤ai≤20000
输入样例:
3
1 2 9
输出样例:
15

queue容器常用函数 top(), pop(), push(), size()

#include <iostream>
#include <queue>using namespace std;int main()
{int n;scanf("%d", &n);priority_queue<int, vector<int>, greater<int>> heap;//小根堆:堆顶最小 while (n -- ){int x;scanf("%d", &x);heap.push(x);}int res = 0;while (heap.size() > 1)//取两个最小的出堆合成新的节点入堆:直到只剩一个节点,huffman树构造完成【WPL最小】{int a = heap.top(); heap.pop();int b = heap.top(); heap.pop();res += a + b;heap.push(a + b);}printf("%d\n", res);return 0;
}

AcWing 913. 排队打水

有 n 个人排队到 1 个水龙头处打水,第 i 个人装满水桶所需的时间是 ti,请问如何安排他们的打水顺序才能使所有人的等待时间之和最小?

输入格式
第一行包含整数 n。

第二行包含 n 个整数,其中第 i 个整数表示第 i 个人装满水桶所花费的时间 ti。

输出格式
输出一个整数,表示最小的等待时间之和。

数据范围
1≤n≤10510^5105,
1≤ti≤10410^4104
输入样例:
7
3 6 1 4 2 5 7
输出样例:
56


队列调度优先级-服务等待时间
每个人都要等待前面打水的人的时间 - 让时间少的先打完, 最大的最后打,不让别人等太久
按照从小到大排序(t1t_1t1​最小–> tnt_ntn​最大) - 最优解
LLres=t1∗(n−1)+t2∗(n−2)+...tn∗1;LL~ res = t_1 * (n - 1) + t_2 * (n - 2) + ... t_n * 1;LL res=t1​∗(n−1)+t2​∗(n−2)+...tn​∗1;

#include <iostream>
#include <algorithm>using namespace std;typedef long long LL; //不开long long 见祖宗const int N = 1e5 + 10;int n;
int t[N];int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++ ) scanf("%d", &t[i]);sort(t, t + n);reverse(t, t + n); //加翻转 -- 等效从大到小  sort(t, t + n, greater<int>())LL res = 0;for (int i = 0; i < n; i ++ ) res += t[i] * i;printf("%lld\n", res);return 0;
}

改个下标

#include <iostream>
#include <algorithm>using namespace std;typedef long long LL; //不开long long 见祖宗const int N = 1e5 + 10;int n;
int t[N];int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++ ) scanf("%d", &t[i]);sort(t, t + n);LL res = 0;for (int i = 0; i < n; i ++ ) res += t[n - 1 - i] * i;printf("%lld\n", res);return 0;
}

AcWing 104. 货仓选址

在一条数轴上有 N 家商店,它们的坐标分别为 A1∼AN。

现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。

为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。

输入格式
第一行输入整数 N。

第二行 N 个整数 A1∼AN。

输出格式
输出一个整数,表示距离之和的最小值。

数据范围
1≤N≤100000,
0≤Ai≤40000
输入样例:
4
6 2 9 1
输出样例:
12


时间复杂度:[数据1e6]最大选取O(logn)算法:如排序
直觉:放在中间:总距离res = ∑(a[n-1-i] - a[i]) 【每轮依次选取第i大和第i小的坐标, 计算差值得距离累加到res】
my证明思想:两点之间线段最短:最贪心:找最短不走多余的路,放在所有货仓的两点之间

常用证明技巧:找数学模型
奇数项就在中间项上,偶数项就在中间两项中间即在所有分组的区间中间,从几何意义距离角度看就是最小值

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;typedef long long LL; //不开long long见祖宗
const int N = 100010;int n;
int a[N];int main()
{scanf("%d", &n);for(int i = 0; i < n; i++) scanf("%d", &a[i]);sort(a, a + n);LL res = 0; //每轮依次选取第i大和第i小的坐标, 计算差值得距离累加到res for(int i = 0; i < n / 2; i++) //【同翻转一样共枚举n / 2次】{res += a[n - i - 1] - a[i];}printf("%lld\n", res);return 0;
}

y总模拟版

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>using namespace std;typedef long long LL;const int N = 100010;int n;
int x[N];int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++ ) scanf("%d", &x[i]);sort(x, x + n);int c = x[n / 2];//货仓放在中间位置LL res = 0;for (int i = 0; i < n; i ++ ) res += abs(x[i] - c);//计算距离和printf("%lld\n", res);return 0;
}

AcWing 125. 耍杂技的牛

农民约翰的 N 头奶牛(编号为 1…N)计划逃跑并加入马戏团,为此它们决定练习表演杂技。

奶牛们不是非常有创意,只提出了一个杂技表演:

叠罗汉,表演时,奶牛们站在彼此的身上,形成一个高高的垂直堆叠。

奶牛们正在试图找到自己在这个堆叠中应该所处的位置顺序。

这 N 头奶牛中的每一头都有着自己的重量 Wi 以及自己的强壮程度 Si。

一头牛支撑不住的可能性取决于它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,现在称该数值为风险值,风险值越大,这只牛撑不住的可能性越高。

您的任务是确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。

输入格式
第一行输入整数 N,表示奶牛数量。

接下来 N 行,每行输入两个整数,表示牛的重量和强壮程度,第 i 行表示第 i 头牛的重量 Wi 以及它的强壮程度 Si。

输出格式
输出一个整数,表示最大风险值的最小可能值。

数据范围
1≤N≤50000,
1≤Wi≤10,000,
1≤Si≤1,000,000,000
输入样例:
3
10 3
2 5
3 3
输出样例:
2


调整法证明

个人感觉贪心题先加减乘除组合先排序,按题意模拟试试输出orz

适用题型:两个变量规律-求∑\sum∑min(W上方−SiW_{上方}-S_iW上方​−Si​),可用从小到大wi+si可用从小到大w_i+s_i可用从小到大wi​+si​,满足max差值max_{差值}max差值​最小】

#include <iostream>
#include <algorithm>
#define x first
#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 50010;int n;
PII cow[N];int main()
{scanf("%d", &n);for (int i = 0; i < n; i ++ ){int s, w;scanf("%d%d", &w, &s);cow[i] = {w + s, w}; //【存储排序】按w[i]+s[i]从小到大排序,危险系数最小}sort(cow, cow + n);int res = -2e9, sum = 0;for (int i = 0; i < n; i ++ ){int s = cow[i].x - cow[i].y, w = cow[i].y;  //这里: s = (s + w) - wres = max(res, sum - s); //选取最小中最大的危险系数; w[i-1] - s (有多余,会不断累加到下一个)sum += w; //危险系数定义;上面牛的重量w[] - 当前牛的强壮值s 【叠罗汉】}printf("%d\n", res);return 0;
}

时空复杂度分析

一般ACM或者笔试题的时间限制是1秒或2秒。
在这种情况下,C++代码中的操作次数控制在 107∼10810^7∼10^8107∼108 为最佳。

下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择:

1.n≤30n≤30n≤30, 指数级别, dfs+剪枝,状态压缩dp

2.n≤100n≤100n≤100 => O(n3)O(n^3)O(n3),floyd,dp,高斯消元

3.n≤1000n≤1000n≤1000 => O(n2),O(n2logn)O(n2),O(n^2logn)O(n2),O(n2logn),dp,二分,朴素版Dijkstra、朴素版Prim、Bellman-Ford

4.n≤10000n≤10000n≤10000 => O(n∗n)O(n∗\sqrt n)O(n∗n​),块状链表、分块、莫队

5.n≤100000n≤100000n≤100000 => O(nlogn)O(nlogn)O(nlogn) => 各种sort,线段树、树状数组、set/map、heap、拓扑排序、dijkstra+heap、prim+heap、Kruskal、spfa、求凸包、求半平面交、二分、CDQ分治、整体二分、后缀数组、树链剖分、动态树

6.n≤1000000n≤1000000n≤1000000 => O(n)O(n)O(n), 以及常数较小的 O(nlogn)O(nlogn)O(nlogn)算法 => 单调队列、 hash、双指针扫描、并查集,kmp、AC自动机,常数比较小的 O(nlogn)O(nlogn)O(nlogn) 的做法:sort、树状数组、heap、dijkstra、spfa

7.n≤107n≤10^7n≤107 => O(n)O(n)O(n),双指针扫描、kmp、AC自动机、线性筛素数

8.n≤109=>O(n)n≤10^9 => O(\sqrt n)n≤109=>O(n​),判断质数

9.n≤1018n≤10^{18}n≤1018 => O(logn)O(logn)O(logn),最大公约数,快速幂,数位DP

10.n≤101000n≤101000n≤101000 => O((logn)2)O((logn)^2)O((logn)2),高精度加减乘除

11.n≤10100000n≤10100000n≤10100000 => O(logk×loglogk)O(logk×loglogk)O(logk×loglogk),k表示位数,高精度加减、FFT/NTT

算法基础课【合集2】相关推荐

  1. 研究百度下拉360下拉搜狗下拉神马下拉头条下拉抖音下拉的优化算法(合集帖)

    研究百度下拉360下拉搜狗下拉神马下拉头条下拉抖音下拉的优化算法(合集) 这个标题有点长哈,哈哈哈哈,看的是不是有点懵呢?缩减一点,就是研究百度.360.搜狗.神马.头条.抖音下拉词框的优化算法. 开 ...

  2. 加密货币工具和算法大合集

    2019独角兽企业重金招聘Python工程师标准>>> 维基百科- 加密货币加密货币是一种利用密码学原理来保证账户之间交易的安全性并且控制货币发行量的数字资产. 现在市面上已经存在各 ...

  3. 珍宝鸭的力扣练习(8):贪心算法练习合集

    1.贪心算法适用的问题 贪心策略适用的前提是:局部最优策略能导致产生全局最优解. 实际上,贪心算法适用的情况很少.一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可 ...

  4. 【java算法】排序算法大合集

    文章目录 排序分类/排序算法的分类 冒泡排序 选择排序 插入排序 希尔排序 交换法 移位法(效率高 快速排序 归并排序 基数排序 排序算法时间复杂度比较 相关术语补充 各个排序的区别总结 刷题 老子的 ...

  5. jiedai算法模板合集(正在肝2021.8.15)

    文章目录 基础模板 常用板子 数学题常用板子 输出挂 fread快读 高精度 分数类 打表压缩 基数排序 杂项 数据结构 树状数组 一维树状数组 二维树状数组 线段树 主席树 线段树合并/裂开 吉司机 ...

  6. 字节跳动年度《算法资料合集》首次公开,限时下载!

    一.50道高频算法题 目前上述内容已打包成完整电子书,具体获取方式如下: 1. 关注下方公众号: 2. 在下方后台回复关键词「50」快速下载: 扫描上方二维码 回复[50] 二.<机器学习图文手 ...

  7. 超分算法小合集之SRCNN、DCSCN、SRDenseNet、SRGAN

    阅读指引 SRCNN DCSCN SRDenseNet SRGAN 论文快速指引: SRCNN:Learning a Deep Convolutional Network for Image Supe ...

  8. 【基础智能优化算法】68种算法大合集+matlab源码+参考资料+永久更新

    目前已更新68种基础算法+matlab源码+参考资料 !!!!!! 2-蝴蝶优化算法 3--海洋捕食者算法 4-鲸鱼优化算法 5-乌燕鸥优化算法 6-灰狼优化算法 7-黑猩猩优化算法 8-原子搜索算法 ...

  9. 蓝桥杯算法训练合集一 1.印章2.拿金币3.数字游戏4.无聊的逗5.礼物

    目录 1.印章(动态规划) 2.拿金币(动态规划) 3.数字游戏(搜索) 4.无聊的逗(状态搜索) 5.礼物(二分法和前缀和) 1.印章(动态规划) 问题描述 共有n种图案的印章,每种图案的出现概率相 ...

  10. 蓝桥杯算法训练合集十三 1.P06022.P07033.逗志芃的危机4.唯一的小可爱5.JOE的矩阵

    目录 1.P0602 2.P0703 3.逗志芃的危机 4.唯一的傻子 5.JOE的矩阵 1.P0602 问题描述 编写一个程序,输入一个4位的自然数,将组成该数的各位数字重新排列,形成一个最大数和一 ...

最新文章

  1. 关于“System.Data.ProviderIncompatibleException”类型的异常
  2. 拟17.56亿控股江南集成 海陆重工加码光伏产业链
  3. 左神算法:二叉树的最大 / 最小深度(普通+Morris遍历进阶)(Java版)
  4. 二次重建基本完成辣!
  5. 【转载】前后端分离的思考与实践(五)
  6. PHP 实现文件下载实例
  7. Sklearn流水线交叉验证以及超参数网格交叉评估基础案例实战-大数据ML样本集案例实战...
  8. 根据缺口的模式选股买股票,python 学习代码
  9. 从零开始学编程(所以说英语也是零)
  10. Problem A: 零起点学算法93——矩阵转置
  11. 建数据库表需要注意哪些点
  12. 历年苹果秋季发布会产品
  13. 中国马铃薯全粉产业经营策略与销售渠道研究报告(2022-2027年)
  14. PC控制台使用-素材管理
  15. 入驻 【集简云开发者平台】,SDK嵌入接口文档介绍
  16. 入职两年涨薪3K被拒,平时好脸给多了?转身立马裸辞走人...
  17. 华为路由器忘记密码_如果忘记密码,如何访问路由器
  18. 码元,数据传输速率,带宽,信噪比,信道容量
  19. 语音识别英语_英语语音识别_英语 语音识别 - 云+社区 - 腾讯云
  20. 概率论与数理统计 浙江大学 第9-15讲单元测验

热门文章

  1. 直播带货系统,带货直播系统中发布商品的逻辑处理流程
  2. matlab butter()函数解析
  3. Ubuntu22.04 中Drag and drop is not supported问题
  4. 智能AI电话机器人系统
  5. 202301读书笔记|《命运》蔡崇达
  6. 别做梦了,社交产品哪有那么容易成功
  7. html5 javascript写法,9 个强大而非主流的JS写法(各种 Hack 写法)
  8. 手Q体验不满意的地方之(3)——个人设置栏
  9. 全域、全要素、全过程的数字化国土空间治理之道
  10. 【密码算法 之七】GCM 浅析