「LOJ #516」「LibreOJ β Round #2」DP 一般看规律

Description

给定一个长度为 (n) 的序列 ,一共有 (m) 个操作。

每次操作的内容为:给定 (x, y),序列中所有 (x) 会变成 (y)

同时我们有一份代码:

int ans = 2147483647;
for (int i = 1; i <= n; i++) {
    for (int j = i + 1; j <= n; j++) {
        if (a[i] == a[j])
            ans = std::min(ans, j - i);
    }
}
std::cout << ans << std::endl;

请在每次修改后输出代码运行的结果。

Hint

(1le n,mle 10^5)

Soluiton

很明显题中所给的代码的意义是,求所有相等元素的间隔距离的最小值。

又注意到,操作进行下去时,颜色种类数不会增加,那么 ans 的值也只能减少。

先离线下来,离散化一下,然后考虑平衡树启发式合并。

我们开 总颜色种类数 个 setpos[i] 表示颜色 (i) 在序列中的位置的集合。

当有操作 x y 时, 就把 pos[x] 的所有元素“倾倒”到 pos[y] 中,同时更新答案。

但这样显然会被卡,直接交上去剩几分看RP

于是乎有了 启发式合并。并不是什么深奥的科技,就是在原算法的基础上,限制只能从小集合“倾倒”到大集合中去。显然比反过来倒要快。注意交换的话,要标记一下(Code 中的 refer)。

启发式合并如何保证复杂度?假设一个元素 (i) 在某个集合中,那么一次次的合并之后(为了方便解释,先假定 (i) 的所在集合总是上述的“小集合”),(i) 的所在集合至少会扩大两倍。但是我们发现这里总共也就 (n) 个元素,于是每个元素被最多合并 (log n) 次。算上平衡树复杂度,就是 (O(nlog^2 n))

虽说两个 (log),但常数不大,而且本来就能过。

Code

注:std::setinsert 函数的返回值是一个 pair,取其 first 可以直接得到插入元素的迭代器。

/*
 * Author : _Wallace_
 * Source : https://www.cnblogs.com/-Wallace-/
 * Problem : LOJ #516 「LibreOJ β Round #2」DP 一般看规律
 */
#include <iostream>
#include <algorithm>
#include <set>

using namespace std;
typedef set<int>::iterator setIt;

const int N = 1e5 + 5;
int n, q, ary[N];
int qry[2][N];
int ans = 2147483647;

int refer[N << 2];
set<int> pos[N << 2];

int val[N << 2];
int cnt = 0;

void merge(int x, int y) {
	if (x == y) return;
	if (pos[refer[x]].size() > pos[refer[y]].size())
		swap(refer[x], refer[y]);
	x = refer[x], y = refer[y];
	/*x -> y*/
	for (setIt itx = pos[x].begin(); itx != pos[x].end(); itx++) {
		setIt ity = pos[y].insert(*itx).first;
		if (++ity != pos[y].end()) ans = min(ans, *ity - *itx);
		if (--ity != pos[y].begin()) ans = min(ans, *itx - *(--ity));
	}
	pos[x].clear();
}

signed main() {
	ios::sync_with_stdio(false);
	cin >> n >> q;
	for (register int i = 1; i <= n; i++)
		cin >> ary[i], val[++cnt] = ary[i];
	for (register int i = 1; i <= q; i++) {
		cin >> qry[0][i] >> qry[1][i];
		val[++cnt] = qry[0][i], val[++cnt] = qry[1][i];
	}
	
	sort(val + 1, val + 1 + cnt), cnt = unique(val + 1, val + 1 + cnt) - val - 1;
	for (register int i = 1; i <= n; i++)
		ary[i] = lower_bound(val + 1, val + 1 + cnt, ary[i]) - val;
	for (register int i = 1; i <= q; i++) {
		qry[0][i] = lower_bound(val + 1, val + 1 + cnt, qry[0][i]) - val;
		qry[1][i] = lower_bound(val + 1, val + 1 + cnt, qry[1][i]) - val;
	}
	
	for (register int i = 1; i <= cnt; i++)
		refer[i] = i;
	
	for (register int i = 1; i <= n; i++) {
		setIt it = pos[ary[i]].insert(i).first;
		if (++it != pos[ary[i]].end()) ans = min(ans, *it - i);
		if (--it != pos[ary[i]].begin()) ans = min(ans, i - *(--it));
	}
	
	for (register int i = 1; i <= q; i++) {
		merge(qry[0][i], qry[1][i]);
		cout << ans << '
';
	}
}

不加启发式合并居然可以在 2s 左右AC??!

原文地址:https://www.cnblogs.com/-Wallace-/p/12779513.html