本文针对有一定算法基础的选手制作,收录了大部分算法的模板,详细解说可以点进去我提供的链接了解。或者进入我的主页
给一点支持!
本人也是一名新手,如果这篇文章有不严谨的地方或者不懂的地方可以在评论区留言,我会为你们一一解答的。
详见高精度算法解析和高精度赛场用模板。
目前没有关于高精度减法和除法的解析,之后会为大家补上。
#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;
}
#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;
}
#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;
}
敬请期待。
在这里,我们通过分装一个bigint
结构体实现存储高精度数字。
在比赛中,我们一般只会用到高精度加法和乘法。所以这里只重载加号和乘号。感兴趣的同学可以试着重载一下减号和除号。
#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;
}
#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;
}
};
详见冒泡排序讲解和优化以及【题解】——车厢重组。
代码只展示函数部分。
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]);
}
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; // 没有发生交换,跳出
}
}
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; // 没有发生交换,跳出
}
}
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;
}
}
一般的冒泡排序在实际应用中最多用到优化一。因为冒泡排序无论怎样优化,时间复杂度总在 O ( n 2 ) O(n^2) O(n2)徘徊。后面两种优化主要是提供给读者扩展思路。
详见选择排序、插入排序讲解和【题解】——欢乐的跳
代码仅展示函数部分。
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; // 基准数归位
}
}
详见选择排序、插入排序讲解和【题解】——欢乐的跳
代码仅展示函数部分。
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]); // 交换最小值到原本的位置上
}
}
详见快速排序介绍和【题解】——【模板】排序
代码仅展示函数部分。
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没有超过右区间,在右区间排序
}
详见计数排序讲解和【题解】——选举学生会
#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;
}
详见【题解】【字符串排序】——宇宙总统
代码仅展示cmp
函数部分。排序时仍然使用sort
。可视具体情况更改。
bool cmp(string a, string b) {
if (a.size() == b.size())return a < b; // 长度不同直接比较字典序
return a.size() < b.size();
}
详见【题解】【结构体排序】—— [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为第一关键字
}
详见【算法讲解】枚举算法介绍和题目推荐。
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++;
}
int a[] = {}, n; // a数组储存数据,需要自行输入
do {
for (int i = 1; i <= n; i++) // 输出序列
cout << a[i] << " ";
puts("");
} while (next_permutation(a + 1, a + n + 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;
}
问题
:给出 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;
}
问题
:有 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;
}
问题
:给定 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;
}
问题
:给定 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;
}
问题
:给定 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;
}
问题
: 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;
}
详见【算法讲解】二分算法介绍和题目推荐。
二分的时候,一般需要对边界判定,条件等琐碎细节做出调整。
代码仅展示函数部分。
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
}
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
}
使用函数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;
}
详见【算法讲解】搜索算法介绍和题目推荐(全网最详细!!!看完这一篇就够了)。
下面展示的代码是通用函数模板,需要根据实际情况做出修改。
// 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; // 恢复现场
}
}
}
下面展示的代码是通用模板,需要根据实际情况做出修改。
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)); // 此状态入队
}
}
}
}
代码只展示函数部分。
#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);
}
}
#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);
}
}
泛洪算法通常(我喜欢)使用 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));
}
}
}
}
详见动态规划最长上升子序列问题讲解和【题解】——最长上升子序列。
#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;
}
详见动态规划最大子段和讲解和【题解】——最大子段和。
#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;
}
#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;
}
详见01背包问题模型讲解和【题解】—— [NOIP2005 普及组] 采药。
#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;
}
#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;
}
详见完全背包模型讲解和【题解】——疯狂的采药(附C++代码实现)。
#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;
}
#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;
}
详见多重背包模型讲解(附C++代码实现)。
#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;
}
详见【动态规划】【背包】多重背包二进制优化(简单易懂)和【题解】——【洛谷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;
}
详见分组背包模型讲解和【题解】——通天之分组背包。
#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;
}
#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;
}
详见【算法讲解】【倍增法的简单应用】——快速幂。
代码只展示函数部分。
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); // 使用位运算更快计算
}
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);
}
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);
}
读者也可自行尝试将快速幂和高精度结合起来。
详见【数学】【基础数论】——整除数论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;
}
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;
}
会可变数组的同学可以尝试使用可变数组改进。下面的代码也是如此。
详见【数学】【基础数论】——整除数论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); } // 求两数最小公倍数的函数
整数唯一分解定理( 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;
}
}
详见【归纳】常见函数模版和解析2。
代码只展示函数部分。
// 前置函数
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";
// 将十进制数转换成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;
}
// 将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;
}
// 将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);
}
详见【归纳】常见函数模版和解析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; // 否则就返回这个数是质数
}
这也是考频比较高的,且应用性较强,应用范围较为广泛。
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;
}
后面的四个函数就没有那么重要了,基本上只在入门时出现,只需要了解原理就行。
bool IsLeapYear(int x) { // 判断是否为闰年的函数
if ((x % 4 == 0 && x % 100 != 0) || x % 400 == 0) // 口诀
return true; // 满足条件就返回真
return false; // 否则返回假
}
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;
}
bool IsPerfectSquare(int n) { // 判断是否为完全平方数的函数
int x = (int)sqrt(n); // 强制转换,如果不是完全平方数则会丢失精度
if (x * x == n) // 如果这个数的平方等于原数
return true; // 返回真
return false;
}
最后,制作不易,希望大家多多点赞收藏,关注下微信公众号,谢谢大家的关注,您的支持就是我更新的最大动力!
公众号上会及时提供信息学奥赛的相关资讯、各地科技特长生升学动态、还会提供相关比赛的备赛资料、信息学学习攻略等。