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;
}

哈希

所谓哈希,就是把一个非常大的取值空间,映射 到一个有限域,使得可以快速查询。一般性的哈希需要满足两个特征, 不可逆无冲突 。当然这种理想情况是不存在的,所以我们只需要尽量满足,难以取得原值,难以构造冲突即可。

在信息学竞赛中我们很少用到加密算法,所以我们只需要满足构造一种映射,使得尽量不会重复映射即可。

  1. 整数哈希

我们可以对每一个取模结果开一个链表,每次出现余数为 \(k\) 的情况就在对应的表头下接入一个新的元素。

每次查询的复杂度是 \(O(\textit{len}_k)\) 的,其中 \(\textit{len}_k\)\(k\) 对应的链表长度。一般情况下可以看做 \(O(1)\)

  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;
	}
}

当然,这种哈希值冲突太严重了,我们一般采用双 MODbase,计算出两套哈希值,满足两套哈希都相等才认为两个子串相等。

离散化

对于一些序列我们 只关注数字的大小关系 的问题,我们可以将数字变成一个有限区间里的数,从而减低时间复杂度。

【基本模板】

使用 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;
}

位运算

  • 一些奇技淫巧:
  1. *2 替换成 <<1/2 替换成 >>1,其他同理。
  2. 找一个数最低的是1的位:#define lowbit(x) (x & (-x))
  3. 循环1的个数次:for (int x; x; x &= x - 1);
  4. 交换两个数:a ^= b, b ^= a, a ^= b;
  • 对于一些可能需要开多位的 bool 类型的问题,我们可以把这些 bool 类型当作二进制压成一个数字表示,这样就可以方便操作。状压 dp 就是一个典型类型。

  • 对于一些二维 dp,维护关于 0/1 可能性 的问题,可以用 bitset 优化。实质上,就是把这些对应的每 \(32\) 个 0/1 压位成一个 int 储存,这样空间常数就会除以 \(32\);转移也可以通过位运算实现,这样时间复杂度就会除以 \(32\)

*生成随机数

关于随机数 - f2021ljh 的博客 - 洛谷博客

生成随机实数代码:

#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;
}

模拟退火(Simulate Anneal, SA)

关于模拟退火 - f2021ljh - 博客园

posted @ 2022-11-06 20:07  f2021ljh  阅读(15)  评论(0)    收藏  举报