【归纳】C++入门算法模版总结(超级详细!!!)(包括高精度,排序,枚举,二分,搜索,动态规划等)

0.前言

    本文针对有一定算法基础的选手制作,收录了大部分算法的模板,详细解说可以点进去我提供的链接了解。或者进入我的主页给一点支持!

    本人也是一名新手,如果这篇文章有不严谨的地方或者不懂的地方可以在评论区留言,我会为你们一一解答的。

【归纳】C++入门算法模版总结(包括高精度,排序,枚举,二分,搜索,动态规划等)(超级详细!!!)

  • 0.前言
  • 1.高精度
    • 1.1.单独实现
      • 1.1.1.高精度加法
      • 1.1.2.高精度减法
      • 1.1.3.高精度乘法
      • 1.1.4.高精度除法和取模
    • 1.2.分装成结构体
      • 1.2.1.前置函数
      • 1.2.2.高精度加法
      • 1.2.3.高精度乘法
      • 1.2.4.整合
  • 2.排序
    • 2.1.冒泡排序
      • 2.1.1.最朴素的模版
      • 2.1.2.一次优化(添加没有产生移动就跳出部分)
      • 2.1.3.二次优化(添加记录最后一次移动位置部分)
      • 2.1.4.三次优化(双向冒泡排序)
      • 2.1.5.Tips
    • 2.2.选择排序
    • 2.3.插入排序
    • 2.4.快速排序
    • 2.5.计数排序
    • 2.6.字符串模拟高精度数字排序
    • 2.7.结构体排序
  • 3.枚举
    • 3.1.子集枚举
    • 3.2.排列枚举
    • 3.3.Tips
  • 4.贪心
    • 4.1.最优选择问题
    • 4.2.部分背包问题
    • 4.3.乘船问题
    • 4.4.选择不相交区间问题
    • 4.5.区间选点问题
    • 4.6.区间覆盖问题
    • 4.7.带限期与罚款的单位时间任务调度
  • 5.二分查找和二分答案
    • 5.1.二分查找
      • 5.1.1.找到第一个出现的 x x x的位置
      • 5.1.2.找到最后一个出现的 x x x的位置
    • 5.2.二分答案
  • 6.搜索
    • 6.1.深度优先搜素
    • 6.2.广度优先搜索
    • 6.3.泛洪算法
      • 6.3.1.四邻域填充法
      • 6.3.2.八邻域填充法
      • 6.3.3.Tips(泛洪算法 b f s bfs bfs实现)
  • 7.简单动态规划
    • 7.1.最长上升子序列
    • 7.2.最大子段和
      • 7.2.1.普通版本
      • 7.2.2.空间复杂度优化版本
    • 7.3.背包问题
      • 7.3.1. 01 01 01背包
        • 7.3.1.1.二维 d p dp dp
        • 7.3.1.2.一维 d p dp dp
      • 7.3.2.完全背包
        • 7.3.2.1.暴力枚举 + + +二维 d p dp dp
        • 7.3.2.2.时间复杂度优化 + + +一维 d p dp dp
      • 7.3.3.多重背包
        • 7.3.3.1.直接展开
        • 7.3.3.2.二进制优化
      • 7.3.4.分组背包
        • 7.3.4.1二维 d p dp dp
        • 7.3.4.2.一维 d p dp dp
  • 8.数学和其他
    • 8.1.快速幂
      • 8.1.1.使用位运算的快速幂
      • 8.1.2.不使用位运算的快速幂
      • 8.1.3.带取模的快速幂
    • 8.2.素数筛法
      • 8.2.1.埃氏筛
      • 8.2.2.线性筛(欧拉筛)
      • 8.2.3.Tips
    • 8.3.最大公因数和最小公倍数
    • 8.4.整数唯一分解定理(质因数分解)
    • 8.5.进制转换
      • 8.5.1.前置函数
      • 8.5.2.将十进制数转换成 n n n进制数的函数
      • 8.5.3.将 n n n进制数转换成十进制数的函数
      • 8.5.4.将 a a a进制数转换成 b b b进制数的函数
    • 8.6.其他常用函数
      • 8.6.1.判断质数
      • 8.6.2.判断回文数
      • 8.6.3.判断闰年
      • 8.6.4.判断完全数
      • 8.6.5.判断完全平方数

1.高精度

    详见高精度算法解析和高精度赛场用模板。

    目前没有关于高精度减法和除法的解析,之后会为大家补上。

1.1.单独实现

1.1.1.高精度加法

#include
using namespace std;
#define MAXN 520
int a[MAXN], b[MAXN], c[MAXN], i, j;
string A, B; // 将两个数字暂时存储到字符串中
int main() {
    cin >> A >> B;
    for (i = A.length() - 1, j = 1; i >= 0; i--, j++) // 倒序存储
        a[j] = A[i] - '0';
    for (i = B.length() - 1, j = 1; i >= 0; i--, j++)
        b[j] = B[i] - '0';
    int len = max(A.length(), B.length());
    for (i = 1; i <= len; i++) { // 计算贡献并处理进位 
        c[i] += a[i] + b[i];
        c[i + 1] = c[i] / 10;
        c[i] %= 10;
    }
    if (c[len + 1]) // 最后进位是否导致位数增加 
        len++;
    for (i = len; i >= 1; i--) // 一定要倒着输出 
        cout << c[i];
    return 0;
}

1.1.2.高精度减法

#include
using namespace std;
#define MAXN 520
int a[MAXN], b[MAXN], c[MAXN], i, j;
string A, B; // 将两个数字暂时存储到字符串中
int main() {
	cin >> A >> B;
	int lena = A.length(), lenb = B.length(), lenc = 0;
	for (i = lena - 1; i >= 0; i--) // 倒序存储
		a[lena - i] = A[i] - '0';
	for (i = lenb - 1; i >= 0; i--)
		b[lenb - i] = B[i] - '0';
	for (int i = 1; i <= max(lena, lenb); i++, lenc++) { // 模拟每一位相减
		if (a[i] < b[i]) { // 如果需要退位
			a[i + 1]--; // 先退位
			c[i] = a[i] + 10 - b[i]; // 再将借到的加上去
		}
		else c[i] = a[i] - b[i]; // 否则直接相减
	}
	while (!c[lenc])
		lenc--;
	for (i = lenc; i >= 1; i--)
		cout << c[i];
	return 0;
}

1.1.3.高精度乘法

#include
using namespace std;
#define MAXN 5010
int a[MAXN], b[MAXN], c[MAXN], i, j;
string A, B; // 将两个数字暂时存储到字符串中
int main() {
    cin >> A >> B;
    int lena = A.length(), lenb = B.length(), len = A.length() + B.length();
    for (i = lena - 1; i >= 0; i--) // 倒序存储
        a[lena - i] = A[i] - '0';
    for (i = lenb - 1; i >= 0; i--)
        b[lenb - i] = B[i] - '0';
    for (i = 1; i <= lena; i++) // 计算贡献 
        for (j = 1; j <= lenb; j++)
            c[i + j - 1] += a[i] * b[j];
    for (i = 1; i <= len; i++) { // 乘积长度不超过两数长度之和 
        c[i + 1] += c[i] / 10; // 处理进位
        c[i] %= 10;
    }
    while (!c[len]) // 去掉前导零
        len--;
    for (i = max(len, 1); i >= 1; i--)
        cout << c[i];
    return 0;
}

1.1.4.高精度除法和取模

    敬请期待。

1.2.分装成结构体

    在这里,我们通过分装一个bigint结构体实现存储高精度数字。

    在比赛中,我们一般只会用到高精度加法和乘法。所以这里只重载加号和乘号。感兴趣的同学可以试着重载一下减号和除号。

1.2.1.前置函数

#define MAXN 1010
struct bigint {
	int len, a[MAXN];
	bigint(int x = 0) { // 默认这个数是0
		memset(a, 0, sizeof(a)); // 初始化数组a
		if (x == 0) { // 如果x是0会直接跳过底下的循环,导致len被初始化为0,所以要特判
			len = 1;
			return; // 直接返回
		}
		for (len = 1; x; len++) // 只要x没被取完就一直去并增加这个数的长度
			a[len] = x % 10, x /= 10; // 倒序存储x
		len--; // 最后len总会多1
	}
	int& operator[](int i) { // 用x[i]代替x.a[i]
		return a[i];
	}
	void in_data() { // 输入高精度整数
		string s; cin >> s; // 暂时存储到字符串里面
		memset(a, 0, sizeof(a)); len = 0; // 清空数组并初始化这个大整数
		for (int i = s.length() - 1; i >= 0; i--, len++) // 字符串转整数
			a[s.length() - i] = s[i] - '0';
	}
	void print() {//打印这个数字,注意数组a是倒着存储各个数位的
		// 注意在后面的处理中可能会出现len=0的情况,所以i的初始值要写成max(num.len, 1)
		for (int i = max(len, 1); i >= 1; i--)
			printf("%d", a[i]); // 前面重载过[]了,等价于num.a[i]
	}
	void flatten(int L) { // 处理1到L范围内的进位并重置长度,需要保证L不小于有效长度
		len = L; // 先赋一个初始值
		for (int i = 1; i <= len; i++) // 处理进位
			a[i + 1] += a[i] / 10, a[i] %= 10;
		while (!a[len]) // 将多余的长度去掉
			len--;
	}
};

1.2.2.高精度加法

// 为了方便演示,这里定义成友元函数,暂时不需要知道为什么,也可以自己上网搜一下
friend bigint operator+(bigint a, bigint b) { // 重载高精度加法
	bigint c; // 存储最终的答案
	int _len = max(a.len, b.len); // 累加
	for (int i = 1; i <= _len; i++) // 循环累加
		c[i] += a[i] + b[i];
	c.flatten(_len + 1); // 计算后最多不超过_len+1位
	return c;
}

1.2.3.高精度乘法

friend bigint operator*(bigint a, bigint b) { // 重载高精度乘法
	bigint c; // 储存答案
	int lena = a.len, lenb = b.len;
	for (int i = 1; i <= lena; i++) // 遍历a的每一位
		for (int j = 1; j <= lenb; j++) // 遍历b的每一位
			c[i + j - 1] += a[i] * b[j]; // 计算贡献
	c.flatten(lena + lenb); // 答案长度不会超过两数长度之和
	return c;
}

1.2.4.整合

#define MAXN 1010
struct bigint {
	int len, a[MAXN];
	bigint(int x = 0) { // 默认这个数是0
		memset(a, 0, sizeof(a)); // 初始化数组a
		if (x == 0) { // 如果x是0会直接跳过底下的循环,导致len被初始化为0,所以要特判
			len = 1;
			return; // 直接返回
		}
		for (len = 1; x; len++) // 只要x没被取完就一直去并增加这个数的长度
			a[len] = x % 10, x /= 10; // 倒序存储x
		len--; // 最后len总会多1
	}
	int& operator[](int i) { // 用x[i]代替x.a[i]
		return a[i];
	}
	void in_data() { // 输入高精度整数
		string s; cin >> s; // 暂时存储到字符串里面
		memset(a, 0, sizeof(a)); len = 0; // 清空数组并初始化这个大整数
		for (int i = s.length() - 1; i >= 0; i--, len++) // 字符串转整数
			a[s.length() - i] = s[i] - '0';
	}
	void print() { // 打印这个数字,注意数组a是倒着存储各个数位的
		// 注意在后面的处理中可能会出现len=0的情况,所以i的初始值要写成max(num.len, 1)
		for (int i = max(len, 1); i >= 1; i--)
			printf("%d", a[i]);// 前面重载过[]了,等价于num.a[i]
	}
	void flatten(int L) { // 处理1到L范围内的进位并重置长度,需要保证L不小于有效长度
		len = L; // 先赋一个初始值
		for (int i = 1; i <= len; i++) // 处理进位
			a[i + 1] += a[i] / 10, a[i] %= 10;
		while (!a[len]) // 将多余的长度去掉
			len--;
	}
	friend bigint operator+(bigint a, bigint b) { // 重载高精度加法
		bigint c; // 存储最终的答案
		int _len = max(a.len, b.len); // 累加
		for (int i = 1; i <= _len; i++) // 循环累加
			c[i] += a[i] + b[i];
		c.flatten(_len + 1); // 计算后最多不超过_len+1位
		return c;
	}
	friend bigint operator*(bigint a, bigint b) { // 重载高精度乘法
		bigint c; // 储存答案
		int lena = a.len, lenb = b.len;
		for (int i = 1; i <= lena; i++) // 遍历a的每一位
			for (int j = 1; j <= lenb; j++) // 遍历b的每一位
				c[i + j - 1] += a[i] * b[j]; // 计算贡献
		c.flatten(lena + lenb); // 答案长度不会超过两数长度之和
		return c;
	}
};

2.排序

2.1.冒泡排序

    详见冒泡排序讲解和优化以及【题解】——车厢重组。

    代码只展示函数部分。

2.1.1.最朴素的模版

void Bubble_sort(int a[], int n) {
	for (int i = 1; i <= n - 1; i++) // n个数排序,只用进行n-1趟
		for (int j = 1; j <= n - i; j++)
			if (a[j] > a[j + 1]) // 比较大小并交换
				swap(a[j], a[j + 1]);
}

2.1.2.一次优化(添加没有产生移动就跳出部分)

void Bubble_sort(int a[], int n) {
	bool flag = 0; // 假设没有发生交换
	for (int i = 1; i <= n - 1; i++) { // n个数排序,只用进行n-1趟
		for (int j = 1; j <= n - i; j++)
			if (a[j] > a[j + 1]) { // 比较大小并交换
				swap(a[j], a[j + 1]);
				flag = 1; // 发生了交换
			}
		if (!flag)break; // 没有发生交换,跳出
	}
}

2.1.3.二次优化(添加记录最后一次移动位置部分)

void Bubble_sort(int a[], int n) {
	bool flag = 0; // 假设没有发生交换
	int pos = n - 1; // 假定当前最后一次发生交换的位置是n-1,即数组全部无序 
	for (int i = 1; i <= n - 1; i++) { // n个数排序,只用进行n-1趟
		int second_pos = pos; // 暂时存储上一个pos
		for (int j = 1; j <= second_pos; j++)
			if (a[j] > a[j + 1]) { // 比较大小并交换
				swap(a[j], a[j + 1]);
				pos = j; // 更新位置
				flag = 1; // 发生了交换
			}
		if (!flag)break; // 没有发生交换,跳出
	}
}

2.1.4.三次优化(双向冒泡排序)

void Bubble_sort(int a[], int n) {
	bool flag = 0; // 假设没有发生交换
	int left = 1, right = n; // 用left定义左边界,right定义右边界(a[left],a[right]有序)
	while (left < right) { // 最后会left=right
		int pos; // 记录当前最后发生交换的位置
		for (int i = left; i < right; i++) // 从左往右扫描
			if (a[i] > a[i + 1]) { // 比较大小并交换
				swap(a[i], a[i + 1]);
				pos = i; // 更新位置
				flag = 1; // 发生了交换
			}
		if (!flag)break; // 没有发生交换,跳出
		right = pos;
		for (int i = right; i > left; i--) // 从右往左扫描
			if (a[i] < a[i - 1]) { // 比较大小并交换
				swap(a[i], a[i - 1]);
				pos = i; // 更新位置
				flag = 1; // 发生了交换
			}
		if (!flag)break; // 没有发生交换,跳出
		left = pos;
	}
}

2.1.5.Tips

    一般的冒泡排序在实际应用中最多用到优化一。因为冒泡排序无论怎样优化,时间复杂度总在 O ( n 2 ) O(n^2) O(n2)徘徊。后面两种优化主要是提供给读者扩展思路。

2.2.选择排序

    详见选择排序、插入排序讲解和【题解】——欢乐的跳

    代码仅展示函数部分。

void Insertion_sort(int a[], int n) { // 进行插入排序
	for (int i = 2; i <= n; i++) {
		int j, tmp = a[i]; // 用tmp储存当前基准数
		for (j = i - 1; j >= 1; j--) // 遍历有序区
			if (a[j] > tmp) // 如果不是合适的位置,将a[j]往后挪一位
				a[j + 1] = a[j];
			else // 否则就已经为基准数找到合适的位置了
				break;
		a[j + 1] = tmp; // 基准数归位
	}
}

2.3.插入排序

    详见选择排序、插入排序讲解和【题解】——欢乐的跳

    代码仅展示函数部分。

void Select_sort(int a[], int n) { // 进行选择排序
	for (int i = 1; i < n; i++) { // 只要进行n-1轮排序
		int flag = i; // 用flag储存当前最小值的下标
		for (int j = i; j <= n; j++) // 遍历后面的数
			if (a[j] < a[flag]) // 更新最小值的下标
				flag = j;
		swap(a[i], a[flag]); // 交换最小值到原本的位置上
	}
}

2.4.快速排序

    详见快速排序介绍和【题解】——【模板】排序

    代码仅展示函数部分。

void qsort(int a[], int left, int right) { // 使用递归函数实现快排
	int tmp = a[(left + right) / 2], i = left, j = right; // 从中间设基准数
	do {
		while (a[i] < tmp) // 哨兵i向左找>=基准数的数
			i++;
		while (a[j] > tmp) // 哨兵j向左找<=基准数的数
			j--;
		if (i <= j) {
			swap(a[i], a[j]);
			i++; j--; // 没必要在下一轮再比较这两个位置的数了
		}
	} while (i <= j);
	if (left < j) qsort(a, left, j); // 哨兵j没有超过左区间,在左区间排序
	if (i < right) qsort(a, i, right); // 哨兵i没有超过右区间,在右区间排序
}

2.5.计数排序

    详见计数排序讲解和【题解】——选举学生会

#include
using namespace std;
int main() {
#define MAXN 1000 // 值域
    int n, pail[MAXN + 10] = {}; // 一定要将数组初始化为0,这是很多初学者都会犯的错误
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        int tmp;
        scanf("%d", &tmp);
        pail[tmp]++; // 放进桶中
    }
    for (int i = 0; i <= MAXN; i++) // 循环到每个可能出现的数、
        while (pail[i]) {
            printf("%d ", i);
            pail[i]--; // 输出了一次,还剩下的数量减一
        }
    return 0;
}

2.6.字符串模拟高精度数字排序

    详见【题解】【字符串排序】——宇宙总统

    代码仅展示cmp函数部分。排序时仍然使用sort。可视具体情况更改。

bool cmp(string a, string b) {
    if (a.size() == b.size())return a < b; // 长度不同直接比较字典序
    return a.size() < b.size();
}

2.7.结构体排序

    详见【题解】【结构体排序】—— [NOIP2009 普及组] 分数线划定

    代码仅展示cmp函数部分。排序时仍然使用sort。可视具体情况更改。

bool cmp(object a, object b) {
    if (a.first == b.first)return a.second < b.second; // second为第二关键字
    return a.first < b.first; // first为第一关键字
}

3.枚举

    详见【算法讲解】枚举算法介绍和题目推荐。

3.1.子集枚举

int U = 1 << n; // U-1表示全集
for (int S = 0; S < U; S++)
    if (__builtin_popcount(S) == k) { // 如果题目没有子集元素个数的限制,这个语句可以去掉 
        for (int i = 1; i <= n; i++)
            if (S & 1 << (i - 1)) // 如果当前第i个元素被取到了子集当中
                // 执行语句
                if (符合条件) ans++;
    }

3.2.排列枚举

int a[] = {}, n; // a数组储存数据,需要自行输入
do {
	for (int i = 1; i <= n; i++) // 输出序列
		cout << a[i] << " ";
	puts("");
} while (next_permutation(a + 1, a + n + 1)); // 重复生成下一个序列 

3.3.Tips

    以上只是一般模板,题目并非是一成不变的,需要根据具体情况改进。

4.贪心

    详见【算法讲解】贪心算法介绍和题目推荐

4.1.最优选择问题

    问题:给出 n n n个物品,第 i i i个物品重量为 w i w_i wi,选择尽量多的物品,使得总重量不超过 C C C

#include
using namespace std;
int main() {
    int n, c, w[20010], ans = 0;
    cin >> n >> c;
    for (int i = 1; i <= n; i++)cin >> w[i];
    sort(w + 1, w + n + 1, greater<int>()); // 从大到小排序
    for (int i = 1; c > 0; c -= w[i++], ans++);
    cout << ans << endl;
    return 0;
}

4.2.部分背包问题

    问题:给出 n n n个物品,第 i i i个物品重量为 w i w_i wi,价值为 v i v_i vi,求出在总重量不超过 C C C的情况下最大的价值。(每一样物品可以分割)

#include
using namespace std;
struct object { // 储存物品的总重量和总价格
	int m, v;
}a[110];
bool cmp(object x, object y) { // 排序规则
	return x.v * y.m > y.v * x.m; // 单价高者优先,这里运用了一个小技巧,可以避免浮点数精度误差
}
int main() {
	int n, t, zm = 0; // 用zm存储当前背包装下的总重量
	double ans = 0; // 用ans存储答案
	scanf("%d%d", &n, &t);
	for (int i = 1; i <= n; i++)scanf("%d%d", &a[i].m, &a[i].v);
	sort(a + 1, a + n + 1, cmp);
	// 贪心策略:优先将性价比高的物品装进背包
	for (int i = 1; i <= n; i++) { // 判断每一个物品
		if (zm + a[i].m > t) { // 如果装不下整个
			ans += (t - zm) * (1.0 * a[i].v / a[i].m); // 将能装下的装进去
			break;
		}
		zm += a[i].m; // 总重量增加
		ans += a[i].v; // 总价格增加
	}
	printf("%.2lf", ans); // 保留两位小数,可自行调整
	return 0;
}

4.3.乘船问题

    问题:有 n n n个人,第 i i i个人重量为 w i w_i wi,每艘船载重为 C C C,最多可乘 2 2 2个人,现在想用最少的船将所有人运走,问船的数量。

#include
using namespace std;
int main() {
    // 用l记录当前最轻的人的下标,r记录当前最重的人的下标,sum记录答案
    int n, w, p[100010], l = 1, r, sum = 0;
    cin >> w >> n;
    for (int i = 1; i <= n; i++)cin >> p[i];
    sort(p + 1, p + n + 1); // 排序
    r = n;
    while (l <= r) {
        if (p[l] + p[r] <= w) // 是否超过上限 
            l++, r--, sum++;
        else
            r--, sum++; // 继续尝试
    }
    cout << sum << endl;
    return 0;
}

4.4.选择不相交区间问题

    问题:给定 n n n个开区间 ( a , b ) (a,b) (a,b),选择尽量多个区间,使得这些区间两两没有公共点。

#include
using namespace std;
struct line { // 存储区间信息
    int start, end;
}a[1000010];
bool cmp(line x, line y) {
    return x.end < y.end; // 右区间越大的越靠前
}
int main() {
    int n, finish = 0, ans = 0; // 用finish存储当前覆盖的区间的右端点
    cin >> n;
    for (int i = 1; i <= n; i++)cin >> a[i].start >> a[i].end;
    sort(a + 1, a + n + 1, cmp);
    for (int i = 1; i <= n; i++) // 循环n个区间
        if (a[i].start >= finish) // 如果端点不重合不冲突
            ans++, finish = a[i].end; // 可以取这一个端点
    cout << ans;
    return 0;
}

4.5.区间选点问题

    问题:给定 n n n个闭区间 [ a , b ] [a,b] [a,b],在数轴上选尽量少的点,使得每个区间内都至少有一个或多个点。

    参考模版(“信奥一本通·种树” A C AC AC代码):

#include
using namespace std;
struct tree { // 定义一个结构体,储存每一个区间的数据,t代表每一个区间需要的点的个数
    int b, e, t;
}a[5010];
bool cmp(tree x, tree y) { // 对于每个区间右端点越小越先操作 
    return x.e < y.e;
}
int main() {
    int n, m, tot = 0; // 用tot记录答案 
    bool v[30010] = {}; // 用v[i]记录第i个区间是否有点
    cin >> n >> m;
    for (int i = 1; i <= m; i++)cin >> a[i].b >> a[i].e >> a[i].t;
    sort(a + 1, a + m + 1, cmp);
    for (int i = 1; i <= m; i++) { // 模拟每一次操作
        int cnt = 0; // 记录目前此区间内点的数量
        for (int j = a[i].b; j <= a[i].e; j++) // 遍历区间
            if (v[j])cnt++;
        if (cnt >= a[i].t)continue; // 满足要求就跳出
        cnt = a[i].t - cnt; // 记录还需要添加多少个点
        for (int j = a[i].e; j >= a[i].b; j--) // 从右向左添加点
            if (!v[j]) { // 如果可以添加点
                v[j] = 1; // 标记
                cnt--; // 还需要添加点的个数
                if (cnt == 0)break; // 添加完了就跳出
            }
    }
    for (int i = 1; i <= n; i++) // 统计种了多少树
        if (v[i])tot++;
    cout << tot << endl;
    return 0;
}

4.6.区间覆盖问题

    问题:给定 n n n个闭区间 [ a , b ] [a,b] [a,b],选择尽量少的区间覆盖一条指定的线段区间 [ s , t ] [s,t] [s,t]

#include
using namespace std;
int n, L, W, s, maxt, ans; // s表示当前覆盖的右端点,t表示当前找到的最远右端点,用ans记录答案
struct line { // 记录一条线段的左右端点
    double l, r;
}a[15010];
bool cmp(line a, line b) { // 排序规则:左端点靠前的靠前 
    return a.l < b.l;
}
int main() {
    cin >> n >> L; // 覆盖一个线段区间[0,L]
    for (int i = 1; i <= n; i++)cin >> a[i].l >> a[i].r;
    sort(a + 1, a + n + 1, cmp); // 排序
    // 没有超过草坪的长就继续寻找
    for (int i = 1; maxt < L; ans++/* 找到符合条件的线段将答案加1 */) {
        // 将当前右端点更新为上一次找到的符合条件的最大右端点 
        for (s = maxt; a[i].l <= s && i <= n; i++) // 在当前右端点内寻找,否则会有线段覆盖不到
            if (a[i].r > maxt)maxt = a[i].r;
        if (maxt == s) { // 没有符合条件的线段
            cout << -1 << endl; // 不能覆盖线段 
            break;
        }
    }
    if (maxt >= L)cout << ans << endl; // 能覆盖线段就输出答案
    return 0;
}

4.7.带限期与罚款的单位时间任务调度

    问题 n n n个任务,每个任务需要一个单位时间执行,任务 i i i在时间 d i d_i di前必须完成,否则要罚款 w i w_i wi元。确定所有任务的执行顺序,使得罚款最少。

#include
using namespace std;
struct game { // 定义结构体储存每个任务的信息
	int val, time;
}a[510];
bool cmp(game a, game b) { // 扣钱多的任务先完成
	return a.val > b.val;
}
int main() {
	int m, n, vis[510] = {}; // 用vis数组储存每一秒有没有在做的任务
	cin >> m >> n;
	for (int i = 1; i <= n; i++)cin >> a[i].time;
	for (int i = 1; i <= n; i++)cin >> a[i].val;
	sort(a + 1, a + n + 1, cmp);
	for (int i = 1; i <= n; i++) { // 安排每一个任务
		bool flag = 1; // 是否要罚款
		for (int j = a[i].time; j >= 1; j--) { // 尽量往后放
			if (vis[j] == 0) { // 这个时间还没有安排任务
				flag = 0; // 不需要罚款
				vis[j] = 1; // 这个时间被安排任务了
				break;
			}
		}
		if (flag) { // 需要罚款
			for (int j = n; j >= 1; j--) { // 尽量往后放
				if (vis[j] == 0) { // 这个时间没有被安排任务
					vis[j] = 1;
					break;
				}
			}
			m -= a[i].val; // 减去罚款
		}
	}
	cout << m << endl;
	return 0;
}

5.二分查找和二分答案

    详见【算法讲解】二分算法介绍和题目推荐。

5.1.二分查找

    二分的时候,一般需要对边界判定,条件等琐碎细节做出调整。

    代码仅展示函数部分。

5.1.1.找到第一个出现的 x x x的位置

int LowerBound(int a[], int l, int r, int x) { // 在[a[l],a[r])中二分查找第一个出现的x的位置
	while (l <= r) {
		int mid = l + r >> 1; // 计算中间值,使用位运算提升效率
		if (a[mid] >= x)r = mid - 1; // 满足条件,压缩右区间
		else l = mid + 1; // 否则压缩左区间
	}
	if (a[l] == x)return l; // 最后满足条件就返回
	return -1; // 没有找到,返回-1
}

5.1.2.找到最后一个出现的 x x x的位置

int UpperBound(int a[], int l, int r, int x) { // 在[a[l],a[r])中二分查找第一个出现的x的位置
	while (l <= r) {
		int mid = l + r >> 1; // 计算中间值,使用位运算提升效率
		if (a[mid] <= x)l = mid + 1; // 满足条件,压缩右区间
		else r = mid - 1; // 否则压缩左区间
	}
	if (a[r] == x)return r; // 最后满足条件就返回
	return -1; // 没有找到,返回-1
}

5.2.二分答案

    使用函数p,作为答案是否满足条件的判断。使用二分答案时,需要保证答案的单调性。这里以洛谷上的[COCI 2011/2012 #5] EKO / 砍树为例。

    代码只展示函数部分。

bool P(int h) { // 判断高度h是否满足条件
	long long sum = 0; // 用sum记录当前砍到的树的高度 
	for (int i = 1; i <= n; i++) // 枚举求和
		if (a[i] >= h) // h米以上的部分收入囊中
			sum += a[i] - h;
	return sum >= m;
}
int binary_search() { //二分主体部分
	int l = 1, r = 4 * 1e5 + 10, mid, ans = 0; // 最大高度为4*1e5 
	while (l <= r) { // 二分
		mid = l + r >> 1; // 计算中间值
		if (P(mid))l = mid + 1, ans = mid; // 记录答案 
		else r = mid - 1;
	}
	return ans;
}

6.搜索

    详见【算法讲解】搜索算法介绍和题目推荐(全网最详细!!!看完这一篇就够了)。

6.1.深度优先搜素

    下面展示的代码是通用函数模板,需要根据实际情况做出修改。

// k代表递归层数,或者说现在已经填到第几个空,同时可能维护必要信息,比如坐标等
void dfs(int k) {
    if (达到终止条件) {
        更新答案/输出解
        return;
    }
    for (枚举所有状态)
        if (这个状态是可以达到的) {
            记录下这个空(保存现场);
            dfs(k + 1); // 递归处理下一层
            取消这个空(恢复现场);
        }
}

    用 d f s dfs dfs解决迷宫问题的示例代码如下。

// 求在一个n*m的有t个障碍的迷宫中从(sx,sy)走到(fx,fy)的方法数
#define MAXN 110
// 用vis[i][j]记录(i,j)是否已经走过,记得vis[sx][sy]=1,mp[i][j]记录(i,j)是否有障碍
int n, m, t, sx, sy, fx, fy, ans, vis[MAXN][MAXN], mp[MAXN][MAXN];
const int dx[] = { 0, 0, -1, 1 }; // x坐标的偏移量
const int dy[] = { -1, 1, 0, 0 }; // y坐标的偏移量
void dfs(int x, int y) {
	if (x == fx && y == fy) { // 如果到达目标点,方案数+1
		ans++;
		return;
	}
	for (int i = 0; i < 4; i++) { // 枚举四个方向
		int px = x + dx[i], py = y + dy[i]; // 记录偏移后的坐标
		// 不超出边界且没有访问过且这里没有障碍
		if (px >= 1 && px <= n && py >= 1 && py <= n && !vis[px][py] && !mp[px][py]) {
			vis[px][py] = 1; // 保存现场
			dfs(px, py); // 走下一步
			vis[px][py] = 0; // 恢复现场
		}
	}
}

6.2.广度优先搜索

    下面展示的代码是通用模板,需要根据实际情况做出修改。

queue<state>Q; // 广度优先搜索需要用到的队列
Q.push(初始信息); // 初始状态入队
while (!Q.empty()) {
    state now = Q.front(); // 取出当前状态
    Q.pop();
    for (枚举所有状态) // 扩展
        if (这个状态是可以达到的)
            Q.push(此状态); // 同时可能维护某些重要信息
}

    用 b f s bfs bfs解决最短路问题的主要代码如下。

// 有一个n*m的棋盘,在某个点(x,y)上有一个马,计算出马到达棋盘上任意一个点最少要走几步。
#define MAXN 400
int n, m, x, y;
// mp表示到达某个点需要的步数,p表示当前点可以扩展出的点的偏移量
int mp[MAXN][MAXN], p[][2] = { {-2,-1},{-2,1},{2,-1},{2,1},{1,2},{-1,-2},{1,-2},{-1,2} };
struct point {
    int x, y, num;
    // 构造函数,构造初始状态,num代表到这个点需要的步数
    point(int _x = 0, int _y = 0, int _num = 0) :x(_x), y(_y), num(_num) {}
};
queue<point>Q; // bfs需要的队列
void bfs() { // bfs主体
    memset(mp, -1, sizeof(mp));//初始化为-1
    Q.push(point(x, y)); // 初始状态入队
    mp[x][y] = 0; // 已经在当前点,只需要0步
    while (!Q.empty()) { // bfs主体
        point now = Q.front(); // 取出当前状态
        Q.pop();
        for (int i = 0; i < 8; i++) { // 遍历可到达的点
            int dx = now.x + p[i][0], dy = now.y + p[i][1]; // 计算下一步到达的点
            if (dx >= 1 && dx <= n && dy >= 1 && dy <= m && mp[dx][dy] == -1) { // 符合条件
                mp[dx][dy] = now.num + 1; // 步数加一
                Q.push(point(dx, dy, now.num + 1)); // 此状态入队
            }
        }
    }
}

6.3.泛洪算法

    代码只展示函数部分。

6.3.1.四邻域填充法

#define MAXN 100 // 值域
int c[MAXN + 10][MAXN + 10], n;
const int dx[4] = { -1, 0, 0, 1 }; // x坐标的偏移量
const int dy[4] = { 0, -1, 1, 0 }; // y坐标的偏移量
// newcl代表要填充的颜色,oldcl代表之前的颜色,(x,y)代表当前坐标
void dfs(int x, int y, int newcl, int oldcl) {
    c[x][y] = newcl;
    for (int i = 0; i < 4; i++) { // 从四个方向枚举
        int px = x + dx[i], py = y + dy[i];
        if (px >= 1 && px <= n && py >= 1 && py <= n && c[px][py] == oldcl)
            dfs(px, py, newcl, oldcl);
    }
}

6.3.2.八邻域填充法

#define MAXN 100 // 值域
int c[MAXN + 10][MAXN + 10], n;
const int dx[8] = { -1, 0, 0, 1, -1, 1, -1, 1 }; // x坐标的偏移量
const int dy[8] = { 0, -1, 1, 0, -1, -1, 1, 1 }; // y坐标的偏移量
// newcl代表要填充的颜色,oldcl代表之前的颜色,(x,y)代表当前坐标
void dfs(int x, int y, int newcl, int oldcl) {
    c[x][y] = newcl;
    for (int i = 0; i < 8; i++) { // 从八个方向枚举
        int px = x + dx[i], py = y + dy[i];
        if (px >= 1 && px <= n && py >= 1 && py <= n && c[px][py] == oldcl)
            dfs(px, py, newcl, oldcl);
    }
}

6.3.3.Tips(泛洪算法 b f s bfs bfs实现)

    泛洪算法通常(我喜欢)使用 d f s dfs dfs编写代码。这里给出使用 b f s bfs bfs编写四邻域填充的示例。

#define MAXN 100 // 值域
int n, m, mp[MAXN + 10][MAXN + 10], sx, sy; // 在n*m的地图中从(sx, sy)开始填充
int p[][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 对于当前格子的偏移量

void bfs(int newcl, int oldcl) { // 洪水填充
    queue<pair<int, int> > q; // pair.first储存当前点的x坐标,以此类推
    q.push(make_pair(sx, sy)); // 初始状态
    while (!q.empty()) {
    	pair<int, int> now = q.front();
    	q.pop();
    	for (int i = 0; i < 4; i++) { // 向周围四个方向扩展
    	    // 计算按当前方向扩展之后得到的坐标
    		int tmpx = now.first + p[i][0], tmpy = now.second + p[i][1];
    		if (tmpx >= 1 && tmpx <= n && tmpy >= 1 && tmpy <= m
			    && mp[tmpx][tmpy] == oldcl ) { // 如果符合要求
			    	mp[tmpx][tmpy] = newcl; // 填充
			    	q.push(make_pair(tmpx, tmpy));
				}
		}
	}
}

7.简单动态规划

7.1.最长上升子序列

    详见动态规划最长上升子序列问题讲解和【题解】——最长上升子序列。

#include
using namespace std;
#define MAXN 5010 // 题目中n的数据范围,可以按情况改动
int main() {
	int n, a[MAXN], dp[MAXN], ans = 0; // 先给ans赋一个最小值
	cin >> n;
	for (int i = 1; i <= n; i++)cin >> a[i];
	for (int i = 1; i <= n; i++) {
		dp[i] = 1; // 初始状态
		for (int j = 1; j < i; j++)
			if (a[j] < a[i]) // 状态转移方程
				dp[i] = max(dp[i], dp[j] + 1);
		ans = max(ans, dp[i]); // 答案
	}
	cout << ans;
	return 0;
}

7.2.最大子段和

    详见动态规划最大子段和讲解和【题解】——最大子段和。

7.2.1.普通版本

#include
using namespace std;
#define MAXN 200010 // 题目中n的数据范围,可以按情况改动
int main() {
    int dp[MAXN], a[MAXN], n, ans = INT_MIN;
    cin >> n;
    for (int i = 1; i <= n; i++)cin >> a[i];
    dp[1] = a[1]; // 初始状态
    for (int i = 2; i <= n; i++) {
        dp[i] = max(a[i], dp[i - 1] + a[i]); // 状态转移方程
        ans = max(ans, dp[i]); // 直接取较大值
    }
    cout << ans;
    return 0;
}

7.2.2.空间复杂度优化版本

#include
using namespace std;
#define MAXN 200010 // 题目中n的数据范围,可以按情况改动
int main() {
    int prev, a[MAXN], n, ans = INT_MIN; // 用prev记录上一个状态
    cin >> n;
    for (int i = 1; i <= n; i++)cin >> a[i];
    prev = a[1]; // 初始状态
    for (int i = 2; i <= n; i++) {
        prev = max(a[i], prev + a[i]); // 状态转移方程
        ans = max(ans, prev); // 直接取较大值
    }
    cout << ans;
    return 0;
}

7.3.背包问题

7.3.1. 01 01 01背包

    详见01背包问题模型讲解和【题解】—— [NOIP2005 普及组] 采药。

7.3.1.1.二维 d p dp dp
#include
using namespace std;
#define MAXM 1010 // m的数据范围,可根据题目修改
#define MAXN 1010 // n的数据范围,可根据题目修改
// m表示背包的重量,n表示物品的数量,用dp[i][j]储存在前i个物品选择放入容量为j的背包时的最大价值
int n, m, dp[MAXN][MAXM];
struct object { // 储存物品的重量和价值
    int weight, value;
}a[MAXN];
int main() {
    scanf("%d%d", &n, &m); // 输入物品数和背包重量
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &a[i].weight, &a[i].value);
    for (int i = 1; i <= n; i++) // 枚举物品
        for (int j = 1; j <= m; j++) // 枚举重量
            // 状态转移方程,每一个物品都可以选择放或不放
            if (j >= a[i].weight) // 装得下
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - a[i].weight] + a[i].value);
            else // 装不下
                dp[i][j] = dp[i - 1][j];
    printf("%d\n", dp[n][m]);
    return 0;
}
7.3.1.2.一维 d p dp dp
#include
using namespace std;
#define MAXM 1010 // m的数据范围,可根据题目修改
#define MAXN 1010 // n的数据范围,可根据题目修改
// m表示背包的重量,n表示物品的数量,dp[j]储存当前放入容量为j的背包时的最大价值
int n, m, dp[MAXM];
struct object { // 储存物品的重量和价值
    int weight, value;
}a[MAXN];
int main() {
    scanf("%d%d", &n, &m); // 输入物品数和背包重量
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &a[i].weight, &a[i].value);
    for (int i = 1; i <= n; i++) // 枚举物品
        for (int j = m; j >= a[i].weight; j--) // 枚举重量
            // 状态转移方程,每一个物品都可以选择放或不放
            dp[j] = max(dp[j], dp[j - a[i].weight] + a[i].value);
    printf("%d\n", dp[m]);
    return 0;
}

7.3.2.完全背包

    详见完全背包模型讲解和【题解】——疯狂的采药(附C++代码实现)。

7.3.2.1.暴力枚举 + + +二维 d p dp dp
#include
using namespace std;
#define MAXM 1010 // m的数据范围,可根据题目修改
#define MAXN 1010 // n的数据范围,可根据题目修改
struct object { // 储存草药的总时间和总价值
    int weight, value;
}a[MAXN];
int n, m, dp[MAXN][MAXM]; // 用dp[i][j]储存只放前i件物品进容量为j的背包时的最大价值
int main() {
    scanf("%d%d", &m, &n);
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &a[i].weight, &a[i].value);
    for (int i = 1; i <= n; i++) // 枚举物品数
        for (int j = 1; j <= m; j++) { // 枚举重量
            dp[i][j] = dp[i - 1][j]; // 先继承上一层的方法数
            for (int k = 1; k * a[i].weight <= j; k++) // 枚举物品件数
                dp[i][j] = max(dp[i][j], dp[i - 1][j - k * a[i].weight] + k * a[i].value);
        }
    printf("%d\n", dp[n][m]);
    return 0;
}
7.3.2.2.时间复杂度优化 + + +一维 d p dp dp
#include
using namespace std;
#define MAXM 1010 // m的数据范围,可根据题目修改
#define MAXN 1010 // n的数据范围,可根据题目修改
struct object { // 储存物品的重量和价值
    int weight, value;
}a[MAXN];
// n表示物品件数,m表示背包容量 
int n, m, dp[MAXM]; // dp[j]表示背包容量为j时的最大价值
int main() {
    scanf("%d%d", &m, &n);
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &a[i].weight, &a[i].value);
    for (int i = 1; i <= n; i++) // 枚举物品件数
        for (int j = a[i].weight; j <= m; j++) // 枚举重量
            dp[j] = max(dp[j], dp[j - a[i].weight] + a[i].value);
    printf("%d\n", dp[m]);
    return 0;
}

7.3.3.多重背包

    详见多重背包模型讲解(附C++代码实现)。

7.3.3.1.直接展开
#include
using namespace std;
#define MAXM 1010 // m的数据范围,可根据题目修改
#define MAXN 1010 // n的数据范围,可根据题目修改
// m表示背包的重量,n表示物品的数量,dp[j]表示当前重量上限为j时的最大价值
int m, n, dp[MAXM], cnt;
struct object {
    int weight, value; // 储存每个物品的重量和价值
    object(int _w = 0, int _v = 0) :weight(_w), value(_v) {};
}a[MAXN];
int main() {
    scanf("%d%d", &m, &n);
    for (int i = 1; i <= n; i++) { // 输入n个物品
        int w, v, m; // 存储当前物品的重量,价值和件数 
        scanf("%d%d", &w, &v, &m);
        for (int j = 1; j <= n; j++)a[++cnt] = object(w, v); // 展开 
    }
    for (int i = 1; i <= cnt; i++)
        for (int j = m; j >= a[i].weight; j--)
            // 枚举所有能装下a[i]的重量
            dp[j] = max(dp[j], dp[j - a[i].weight] + a[i].value);
    printf("%d\n", dp[m]);
    return 0;
}
7.3.3.2.二进制优化

    详见【动态规划】【背包】多重背包二进制优化(简单易懂)和【题解】——【洛谷P1776】宝物筛选。

#include
using namespace std;
#define MAXN 100010 // m的数据范围,可根据题目修改
// cnt储存当前物品数量,dp[i]储存当前背包容量为j时的最大值
int n, W, cnt = 1, dp[MAXN];
struct object{ // 储存每一个物品的信息 
	int v, w; // 价值和重量 
	object(int _v = 0, int _w = 0) : v(_v), w(_w){}
}a[MAXN];
int main(){
    cin >> n >> W;
    for (int i = 1; i <= n; i++){
    	int v, w, m;
    	cin >> v >> w >> m;
    	for (int j = 1; j <= m; j <<= 1){ // 二进制展开 
    		a[++cnt] = object(v * j, w * j);
    		m -= j;
		}
		if (m) a[++cnt] = object(v * m, w * m); // 还有剩下的 
	}
	for (int i = 1; i <= cnt; i++) // 01背包 
	    for (int j = W; j >= a[i].w; j--)
	        dp[j] = max(dp[j], dp[j - a[i].w] + a[i].v);
	cout << dp[W];
    return 0;
}

7.3.4.分组背包

    详见分组背包模型讲解和【题解】——通天之分组背包。

7.3.4.1二维 d p dp dp
#include
using namespace std;
#define MAXM 1010 // m的数据范围,可根据题目修改
#define MAXN 1010 // n的数据范围(也作最大组数),可根据题目修改
// 用n储存物品件数,m储存背包重量
// 用dp[i][j]背包容量为j只选前i组物品的最大价值,num[i]表示第i组的物品个数 
int n, m, dp[MAXN][MAXM], num[MAXN], cnt; // cnt储存物品组数 
struct object { // 储存物品的重量和价值
	int weight, value;
}a[MAXN][MAXN]; // a[i][j]表示第i组的第j件物品 
int main() {
	scanf("%d%d", &m, &n);
	for (int i = 1; i <= n; i++) {
		int w, v, l;
		scanf("%d%d%d", &w, &v, &l);
		cnt = max(cnt, l); // 更新组数 
		a[l][++num[l]].weight = w; // 储存当前物品的重量和大小 
		a[l][num[l]].value = v;
	}
	for (int i = 1; i <= cnt; i++) // 枚举组数 
		for (int j = 1; j <= m; j++) { // 枚举重量
			dp[i][j] = dp[i - 1][j]; // 先继承上一层的状态 
			for (int k = 1; k <= num[i]; k++) // 枚举每一组的物品
				if (a[i][k].weight <= j) // 符合条件,状态转移
					dp[i][j] = max(dp[i][j], dp[i - 1][j - a[i][k].weight] + a[i][k].value);
		}
	printf("%d", dp[cnt][m]); // 注意输出
	return 0;
}
7.3.4.2.一维 d p dp dp
#include
using namespace std;
#define MAXM 1010 // m的数据范围,可根据题目修改
#define MAXN 1010 // n的数据范围(也作最大组数),可根据题目修改
/* 用n储存物品件数, m储存背包重量
   用dp[i][j]背包容量为j只选前i组物品的最大价值,num[i]表示第i组的物品个数 */
int n, m, dp[MAXM], num[MAXN], cnt;//cnt储存物品组数 
struct object { // 储存每件物品的重量和价值
    int weight, value;
}a[MAXN][MAXN]; // a[i][j]表示第i组的第j件物品 
int main() {
    scanf("%d%d", &m, &n);
    for (int i = 1; i <= n; i++) {
        int w, v, l;
        scanf("%d%d%d", &w, &v, &l);
        cnt = max(cnt, l); // 更新组数 
        a[l][++num[l]].weight = w; // 储存当前物品的重量和大小 
        a[l][num[l]].value = v;
    }
    for (int i = 1; i <= cnt; i++) // 枚举组数 
        for (int j = m; j >= 0; j--) // 枚举重量
            for (int k = 1; k <= num[i]; k++) // 枚举每一组的物品
                if (a[i][k].weight <= j) // 符合条件,状态转移
                    dp[j] = max(dp[j], dp[j - a[i][k].weight] + a[i][k].value);
    printf("%d", dp[m]); // 注意输出
    return 0;
}

8.数学和其他

8.1.快速幂

    详见【算法讲解】【倍增法的简单应用】——快速幂。

    代码只展示函数部分。

8.1.1.使用位运算的快速幂

long long qpow(int x, int y) { // 计算快速幂的函数
    if (y == 0)return 1; // x^0=1
    return y & 1 ? x * qpow(x, y - 1) : qpow(x * x, y >> 1); // 使用位运算更快计算
}

8.1.2.不使用位运算的快速幂

long long qpow(int x, int y) {
	if (y == 0)return 1; // x^0=1
	return y % 2 == 0 ? qpow(x * x, y / 2) : x * qpow(x, y - 1);
}

8.1.3.带取模的快速幂

long long mod = 100007; // 可自行选择模数
long long qpow(int x, int y) { // 计算快速幂的函数
	if (y == 0)return 1; // x^0=1
	// 注意每一步都要对mod取模,不然会爆掉
	return y & 1 ? x * qpow(x, y - 1) % mod : qpow(x * x % mod, y >> 1);
}

    读者也可自行尝试将快速幂和高精度结合起来。

8.2.素数筛法

    详见【数学】【基础数论】——整除数论1。

    代码只展示函数部分。

8.2.1.埃氏筛

int primes[100010], tot = 0;
void Eratosthenes_sieve(int n) { // 埃氏筛
	// 建立一个质数表,用is_prime[i]代表i是否是质数,先假设全部是质数
	bool is_prime[n + 1];
	memset(is_prime, 1, sizeof(is_prime));
	is_prime[0] = is_prime[1] = 0; // 0和1做特殊处理
	for (int i = 2; i * i <= n; i++) // 枚举每一个数
		for (int j = 2; i * j <= n; j++) // 枚举它的倍数
			is_prime[i * j] = 0; // 将他的倍数划掉
	for (int i = 2; i <= n; i++) // 从2开始枚举
		if (is_prime[i]) // i是质数
			primes[++tot] = i;
}

8.2.2.线性筛(欧拉筛)

int primes[100010], tot = 0;
void Leaner_sieve(int n) { // 线性筛
	// 建立一个质数表,用is_prime[i]代表i是否是质数,先假设全部是质数
	bool is_prime[n + 1];
	memset(is_prime, 1, sizeof(is_prime));
	is_prime[0] = is_prime[1] = 0;
	// 枚举每一个数,由于质数数的分布较为稀疏,先对n/2内的因数处理就行了
	for (int i = 2; i <= n / 2; i++) {
		if (is_prime[i])primes[++tot] = i; // i是质数
		for (int j = 1; j <= tot && i * primes[j] <= n; j++) { // 枚举它的质数倍
			is_prime[i * primes[j]] = 0; // 将他的质数倍划掉
			if (i % primes[j] == 0)break;
		}
	}
	for (int i = n / 2 + 1; i <= n; i++) // 把n/2后面的质数统计下来
		if (is_prime[i])
			primes[++tot] = i;
}

8.2.3.Tips

    会可变数组的同学可以尝试使用可变数组改进。下面的代码也是如此。

8.3.最大公因数和最小公倍数

    详见【数学】【基础数论】——整除数论1。

    代码只展示函数部分。

int gcd(int a, int b) { return b == 0 ? a : gcd(min(a, b), max(a, b) % min(a, b)); } // 求两数最大公因数的函数
int lcm(int a, int b) { return a * b / gcd(a, b); } // 求两数最小公倍数的函数

8.4.整数唯一分解定理(质因数分解)

    整数唯一分解定理( U n i q u e   F a c t o r i z a t i o n   T h e o r e m Unique\space Factorization\space Theorem Unique Factorization Theorem)表明,任何一个大于1的整数都可以分解成若干个质数的连乘积,且这种分解是唯一的。具体来说,如果n是一个大于 1 1 1的整数,那么可以表示为: n = ∏ i = 1 k p i e i n = \prod_{i=1}^{k} p_i^{e_i} n=i=1kpiei其中, p i p_i pi是质数, e i e_i ei是正整数。这种分解是唯一的,即不同的质数分解方式不会导致相同的乘积。

#define MAXN 110
// 用p储存质因子,a储存质因子的个数,tot表示质因子的个数
int p[MAXN], a[MAXN], tot = 0;
void getfac(int x) { // 分解质因数的函数
	for (int i = 2; i * i <= x; i++) // 只需要枚举到sqrt(x)
		if (x % i == 0) { // x有这个质因子
			p[++tot] = i;
			while (x % i == 0) {
				a[tot]++;
				x /= i;
			}
		}
	if (x > 1) { // 把没分完的数分完
		p[++tot] = x;
		a[tot] = 1;
	}
}

8.5.进制转换

    详见【归纳】常见函数模版和解析2。

    代码只展示函数部分。

8.5.1.前置函数

// 前置函数
string reverse(string str) { // 将字符串反转的函数 
	string ans;
	for (int i = str.length() - 1; i >= 0; i--)
		ans += str[i];
	return ans;
}

int match(char tmp) { // 配对字符或数字对应的数字 
	if (tmp >= '0' && tmp <= '9')return tmp - '0';
	else return tmp - 'A' + 10;
}

string binary_num = "0123456789ABCDEF";

8.5.2.将十进制数转换成 n n n进制数的函数

// 将十进制数转换成n进制数的函数
string ten_to_n_binary(int num, int n) {
	string ans;
	while (num) { // 没有取完就一直取
		ans += binary_num[num % n];
		num /= n;
	}
	ans = reverse(ans); // 最后要反转一次
	return ans;
}

8.5.3.将 n n n进制数转换成十进制数的函数

// 将n进制数num转换成十进制数的函数
int n_to_ten_binary(string num, int n) {
	int ans;
	for (int i = 0; i < num.length(); i++) // 循环每一位 
		ans += pow(n, num.length() - i - 1) * match(num[i]); // 展开 
	return ans;
}

8.5.4.将 a a a进制数转换成 b b b进制数的函数

// 将a进制数转换成b进制数的函数
string decimal_conversion(string num, int a, int b) {
	int num_ten_binary = n_to_ten_binary(num, a); // 使用一个媒介
	return ten_to_n_binary(num_ten_binary, b);
}

8.6.其他常用函数

    详见【归纳】常见函数模版和解析1。

    代码只展示函数部分。

    这里展示的代码与我给出的文章有所出入。因为这篇文章是我很久以前写的了,里面有一些细节进行了改动,但是大体思路不变。

8.6.1.判断质数

    这是我们平常写题中用到的频率最高的函数,需要熟记。

bool IsPrime(int x) { // 判断是否为质数的函数
	if (x < 2)return false; // 小于2的数都不是质数
	for (int i = 2; i * i <= x; i++) // 只需要枚举到sqrt(x)就行了
		if (x % i == 0) // 如果枚举的这个数能被i整除
			return false; // 返回这个数不是质数
	return true; // 否则就返回这个数是质数
}

8.6.2.判断回文数

    这也是考频比较高的,且应用性较强,应用范围较为广泛。

bool IsPalindrome(int num) { // 判断是否为回文数的函数
	int num1 = 0, n;
	if (num / 10 == 0)return true; // 个位数也是回文数
	// 因为num最后要用来判断,所以不能直接使用它。
	while (num) { // 只要没有取完就一直取
		num1 = num1 * 10 + num % 10; // 更新num1
		num = num / 10; // 取完一位,除以10
	}
	if (num1 == num)return true; // 如果相等就返回真
	return false;
}

8.6.3.判断闰年

    后面的四个函数就没有那么重要了,基本上只在入门时出现,只需要了解原理就行。

bool IsLeapYear(int x) { // 判断是否为闰年的函数
	if ((x % 4 == 0 && x % 100 != 0) || x % 400 == 0) // 口诀
		return true; // 满足条件就返回真
	return false; // 否则返回假
}

8.6.4.判断完全数

bool IsPerfectNum(int s) { // 判断是否为完全数的函数
	int m = 0; // 用m存储这个数所有的真因子之和
	if (s < 6)return false; // 最小的完全数是6,小于6的都不是完全数
	// 枚举小于这个数的数,注意不能枚举到这个数本身
	for (int i = 1; i < s; i++)
		if (s % i == 0) // 如果枚举的这个数是s的因子
			m += i; // 加上
	if (m == s)return true; // 相等就返回真
	return false;
}

8.6.5.判断完全平方数

bool IsPerfectSquare(int n) { // 判断是否为完全平方数的函数
	int x = (int)sqrt(n); // 强制转换,如果不是完全平方数则会丢失精度
	if (x * x == n) // 如果这个数的平方等于原数
		return true; // 返回真
	return false;
}

最后,制作不易,希望大家多多点赞收藏,关注下微信公众号,谢谢大家的关注,您的支持就是我更新的最大动力!
公众号上会及时提供信息学奥赛的相关资讯、各地科技特长生升学动态、还会提供相关比赛的备赛资料、信息学学习攻略等。

你可能感兴趣的:(【归纳】C++入门算法模版总结(超级详细!!!)(包括高精度,排序,枚举,二分,搜索,动态规划等))