NOIP2022多校集训 Day 1
基础算法
二分
以 \(O(\log_2 N)\) 的时间实现 在一个 有序 数组中查找一个特定元素。
【基本模板】
- 整数二分
int l = 0, r = MAXN + 1, mid, ans;
while (l < r) {
mid = (l + r) >> 1;
if (check(mid)) l = mid + 1;
else r = mid;
}
ans = l - 1;
- 浮点二分
double l = 0, r = MAXN, mid, ans, epsilon; //epsilon: precision
while (r - l > epsilon) {
mid = (l + r) / 2;
if (check(mid)) l = mid;
else r = mid;
}
ans = l;
三分
在函数满足 单峰(上凸)或单谷(下凹) 的情况下求极值的算法。
时间复杂度 \(O(\log N)\) 。
【基本模板】
double l = 0, r = MAXN, p, q, epsilon; //p, q: 三等分点
while (r - l > epsilon) {
p = (2 * l + r) / 3, q = (l + 2 * r) / 3;
if (f(p) > f(q)) r = q;
else l = p;
}
分治
对于一个规模为 \(n\) 的问题,若该问题可以容易地解决(\(n\) 较小),则直接将其解决,否则将其分解为 \(k\) 个规模较小的子问题,这些子问题 互相独立 且与原问题 形式相同 ,递归解决这些子问题,然后将各个子问题的解合并,得到原问题的解。
时间复杂度 \(O(N\log N)\) 。
【基本模板】
归并排序
void mergeSort(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
mergeSort(l, mid), mergeSort(mid + 1, r);
int li = l, ri = mid + 1, t = l;
while (li <= mid && ri <= r) {
if (a[li] <= a[ri]) b[t++] = a[li++];
else b[t++] = a[ri++];
}
while (li <= mid) b[t++] = a[li++];
while (ri <= r) b[t++] = a[ri++];
for (int i = l; i <= r; i++) a[i] = b[i];
}
【应用】P1908 逆序对
#include <cstdio>
using namespace std;
const int N = 5e5+10;
int n, a[N], b[N];
long long cnt;
void mergeSort(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
mergeSort(l, mid), mergeSort(mid + 1, r);
int li = l, ri = mid + 1, t = l;
while (li <= mid && ri <= r) {
if (a[li] <= a[ri]) b[t++] = a[li++];
else b[t++] = a[ri++], cnt += mid - li + 1;
}
while (li <= mid) b[t++] = a[li++];
while (ri <= r) b[t++] = a[ri++], cnt += mid - li + 1;
for (int i = l; i <= r; i++) a[i] = b[i];
}
int main() {
// freopen("P1908_6.in", "r", stdin);
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
mergeSort(1, n);
printf("%d\n", cnt);
return 0;
}
哈希
所谓哈希,就是把一个非常大的取值空间,映射 到一个有限域,使得可以快速查询。一般性的哈希需要满足两个特征, 不可逆 和 无冲突 。当然这种理想情况是不存在的,所以我们只需要尽量满足,难以取得原值,难以构造冲突即可。
在信息学竞赛中我们很少用到加密算法,所以我们只需要满足构造一种映射,使得尽量不会重复映射即可。
- 整数哈希
我们可以对每一个取模结果开一个链表,每次出现余数为 \(k\) 的情况就在对应的表头下接入一个新的元素。
每次查询的复杂度是 \(O(\textit{len}_k)\) 的,其中 \(\textit{len}_k\) 为 \(k\) 对应的链表长度。一般情况下可以看做 \(O(1)\)。
- 字符串哈希
【基本模板】
int const base = 131, MOD = 1e9 + 7;
int ha[N], pw[N]; //ha[i]: 前缀串str[1..i]的哈希值 pw[i]: pow(base, i)
int cal (int l, int r) { //子串str[l..r]的哈希值
return MOD + (ha[r] - ha[l - 1] * pw[r - l + 1]) % MOD;
}
void makeHash() {
for (int i = 1; i < n; i++) {
ha[i] = (ha[i - 1] * base + str[i]) % MOD;
pw[i] = pw[i - 1] * base % MOD;
}
}
当然,这种哈希值冲突太严重了,我们一般采用双 MOD 双 base,计算出两套哈希值,满足两套哈希都相等才认为两个子串相等。
离散化
对于一些序列我们 只关注数字的大小关系 的问题,我们可以将数字变成一个有限区间里的数,从而减低时间复杂度。
【基本模板】
使用 C++ stl 库中的 map:
#include <cstdio>
#include <map>
using namespace std;
int n, a[N], tot, x;
map<int, int> mp;
signed main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
mp[a[i]] = 0;
}
for (auto it: mp) { //离散后每个数映射的编号
it.second = ++tot;
}
while (1) { //查询某个数的编号
scanf("%d", &x);
//相当于mp.find(x), 每次调用时间 O(logN)
printf("%d\n", mp[x]);
}
return 0;
}
位运算
- 一些奇技淫巧:
- 把
*2替换成<<1,/2替换成>>1,其他同理。 - 找一个数最低的是1的位:
#define lowbit(x) (x & (-x)) - 循环1的个数次:
for (int x; x; x &= x - 1); - 交换两个数:
a ^= b, b ^= a, a ^= b;
-
对于一些可能需要开多位的
bool类型的问题,我们可以把这些bool类型当作二进制压成一个数字表示,这样就可以方便操作。状压 dp 就是一个典型类型。 -
对于一些二维 dp,维护关于 0/1 可能性 的问题,可以用
bitset优化。实质上,就是把这些对应的每 \(32\) 个 0/1 压位成一个int储存,这样空间常数就会除以 \(32\);转移也可以通过位运算实现,这样时间复杂度就会除以 \(32\)。
*生成随机数
生成随机实数代码:
#include <cstdio>
#include <random>
using namespace std;
typedef double db;
random_device R; //定义生成种子的类
mt19937 gen(R()); //定义生成随机数的类
inline db dbrand(db l, db r) {
uniform_real_distribution<> dis(l,r); //生成随机实数的范围
return dis(gen);
}
int main() {
int n;
db l, r;
scanf("%d%lf%lf", &n, &l, &r);
putchar('\n');
for (int i = 1; i <= n; i++)
printf("%.6lf%c", dbrand(l, r), "\n "[(bool)(i % 10)]); //每10个一换行
return 0;
}

浙公网安备 33010602011771号