Loading

CF1142B Lynyrd Skynyrd

我来讲一讲我的做法,时间复杂度 \(O(n \log n)\)
适宜阅读

2024/7/26 upd:都快写成流水账了。


首先,我们定义 \(p\) 数组中一个数字 \(p_i\) 的下一个数字为:

对于 \(1 \le i \le n - 1\)\(p_i\) 的下一位为 \(p_{i + 1}\)

对于 \(i = n\)\(p_i\) 的下一位为 \(p_1\)

我们将这个数字记为 \(f(p_i)\)

比如 \([1, 3, 4, 5, 2]\)

\(1\) 的下一位就是 \(3\)\(f(1) = 3\)

\(3\) 的下一位就是 \(4\)\(f(3) = 4\)

\(4\) 的下一位就是 \(5\)\(f(4) = 5\)

\(5\) 的下一位就是 \(2\)\(f(5) = 2\)

\(2\) 的下一位就是 \(1\)\(f(2) = 1\)。(注意这里)


然后我们回到 \(a\) 数组。

对于每个数字 \(a_i\),我们将 \(i\) 和在 \(i\) 右边第一个 \(f(a_i)\) 所在位置相连。

比如 \(p = [3, 1, 2], a = [3, 2, 1, 3, 1, 2]\)

首先 \(f(3) = 1, f(1) = 2, f(2) = 3\)

然后我们开始连边。

我们开始枚举 \(a\) 数组。

\(a_1 = 3\),查询得 \(f(3) = 1\),找到 \(3\) 右边第一个 \(1\),发现在位置 \(3\),我们将点 \(1\) 和点 \(3\) 连接。

\(a_2 = 2\),查询得 \(f(2) = 3\),找到 \(2\) 右边第一个 \(3\),发现在位置 \(4\),我们将点 \(2\) 和点 \(4\) 连接。

\(a_3 = 1\),查询得 \(f(1) = 2\),找到 \(1\) 右边第一个 \(2\),发现在位置 \(6\),我们将点 \(3\) 和点 \(6\) 连接。

\(a_4 = 3\),查询得 \(f(3) = 1\),找到 \(3\) 右边第一个 \(1\),发现在位置 \(5\),我们将点 \(4\) 和点 \(5\) 连接。

\(a_5 = 1\),查询得 \(f(1) = 2\),找到 \(1\) 右边第一个 \(2\),发现在位置 \(6\),我们将点 \(5\) 和点 \(6\) 连接。

对于找不到下一个数字在哪里的直接连到 \(m + 1\) 上面去。

\(a_6 = 2\),查询得 \(f(2) = 3\),发现 \(6\) 号位后没有数字 \(2\),我们就将点 \(6\) 和点 \(m + 1 = 7\) 相连。


然后,我们可以发现,如果从一个点沿着刚刚连的边跳 \(n - 1\) 次还没跳到 \(m + 1\),说明从它开始跳 \(n - 1\) 步所经过的所有点就可以构成一个序列,而不会跑到外面去。

比如从第 \(1\) 个点,我们接下来跳 \(n - 1 = 2\) 步:从位置 \(1\) 跳到位置 \(3\),从位置 \(3\) 跳到位置 \(6\),发现 \(6 < m + 1 = 7\),这说明什么?这说明一定跳出了一个序列!这个序列就是 \(3, 1, 2\),分别在位置(索引)\(1, 3, 6\)

再比如从第 \(2\) 个点跳 \(n - 1 = 2\) 步:从位置 \(2\) 跳到位置 \(4\),从位置 \(4\) 跳到位置 \(5\),发现 \(5 < m + 1 = 7\),这说明可以构成一个题目要求的序列,即 \(2, 3, 1\),分别在位置(索引)\(2, 4, 5\)

但是,每个点都跳 \(n - 1\) 步实在是太慢啦!我们套路地选择倍增,参考倍增求 LCA 的做法即可,这里不多说了。


然后我们就得出了每个点 \(i\)\(n - 1\) 步到达点,我们将它记为 \(g(i)\)

对于每个询问 \(l, r\),我们可以枚举从 \(l, r\) 的每一个点 \(i\),即 \(l \le i \le r\),如果 \(g(i) \le r\),就说明从点 \(i\) 开始跳,跳 \(n - 1\) 步不会超过 \(r\),那么整个序列也就在 \(l\)\(r\) 这段之间。

对于每个询问,每次枚举 \(l, r\),时间爆炸!\(O(nq)\) 的时间复杂度直接让我们之前全白干了。

所以我们又套路地选择 RMQ(ST 表) 来求解,ST 表的基础用法在这里不再赘述。

用 ST 表求出从 \(l\)\(r\) 间最小的 \(g(i)\),如果它的最小值比 \(r\) 小,说明一定存在序列在 \(l\)\(r\) 间。


时间复杂度:

倍增:\(O(n \log n)\),ST 表初始化:\(O(n \log n)\),询问:\(O(q)\)

总时间复杂度:\(O(n \log n)\)

代码

注意:第 40 个点 \(m < n\) 坑人,注意初始化时选择 \(n\) 还是选择 \(m\)

// Problem: Lynyrd Skynyrd
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1142B
// Memory Limit: 250 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>

using namespace std;

const int N = 200010;	// N 个点

int n, m, q;			// n, m, q
int a[N], b[N];			// a 数组, b 数组
int lst[N], nxt[N];		// lst 记录一个数字最近的位置。 nxt 记录 a 数组中下一个数字,相当于 f(n)。
int fa[25][N];			// 树上倍增
int jump[N];			// jump[i] 记录从点 i 跳 n - 1 步到达的点

int jump_n_steps(int u, int step) {	// 求点 i 跳 n - 1 步到达的点
	for (int i = 24; i >= 0; i--) {	// 套路,不多说了
		if (step >> i & 1) {
			u = fa[i][u];
		}
	}
	return u;
}

int st[25][N];						// 存储 ST 表

void getst() {						// 初始化 ST 表,不多说
	for (int j = 1; (1 << j) <= m; j++) {
		for (int i = 1; i + (1 << j) - 1 <= m; i++) {
			st[j][i] = min(st[j - 1][i], st[j - 1][i + (1 << j - 1)]);
		}
	}
}

int query(int l, int r) {			// ST 表操作:询问 l, r 的最小的 g(i),不多说
	int len = r - l + 1;
	if (len <= 0) return 0x3f3f3f3f;
	int lg = __lg(len);
	return min(st[lg][l], st[lg][r - (1 << lg) + 1]);
}

int main() {
	ios::sync_with_stdio(false);	// 优化输入
	cin.tie(nullptr);				// 优化输入
	
	cin >> n >> m >> q;				// 输入
	for (int i = 1; i <= n; i++) {	// 输入
		cin >> a[i];
		if (i > 1) nxt[a[i - 1]] = a[i];	// 对于 1 ~ n - 1, p[i] 的下一个数字为 p[i + 1]
	}
	nxt[a[n]] = a[1];						// 对于点 n, p[n] 的下一个数字为 p[1] 
	for (int i = 1; i <= m + 1; i++) fa[0][i] = m + 1;// 初始化第一个点跳出的位置为 m + 1
	for (int i = 1; i <= n; i++) lst[i] = m + 1;	// 第 40 个测试数据坑人,这里初始化用 n
	for (int i = 1; i <= m; i++) cin >> b[i];		// 输入
	for (int i = m; i >= 1; i--) {			// 连边
		fa[0][i] = lst[nxt[b[i]]];
		lst[b[i]] = i;
	}
	
	for (int j = 1; j < 25; j++) {
		for (int i = m + 1; i >= 1; i--) {
			fa[j][i] = fa[j - 1][fa[j - 1][i]];		// 树上倍增,不多说
		}
	}
	
	for (int i = 1; i <= m; i++) jump[i] = jump_n_steps(i, n - 1); // 跳 n - 1 步
	for (int i = 1; i <= m; i++) st[0][i] = jump[i];				// 记录 g(i)
	getst();							// 初始化 ST 表
	
	while (q--) {
		int l, r;
		cin >> l >> r;
		cout << (query(l, r) <= r); 	// 询问
	}
	return 0;
}
posted @ 2024-08-08 12:14  SunnyYuan  阅读(4)  评论(0编辑  收藏  举报