Loading

【题解】P3295 - [SCOI2016] 萌萌哒

题目大意

题目链接

给定一个由 \(n\) 个数组成的序列 \(s\) 。已知有 \(m\) 个限制,每个限制 \(l_1, r_1, l_2, r_2\) 表示 \(s\) 满足 \([s_{l1}, s_{r1}]\)\([s_{l2}, s_{r2}]\) 完全相同。试求一共有多少种构造序列的方案同时满足所有的 \(m\) 个限制,结果对 \(10^9 + 7\) 取模。

\(1 \leq n \leq 10^5, 1 \leq m \leq 10^5\)

解题思路

这道题的经验教训告诉我们,做题不要拘泥于形式,要从自己的经验和直觉入手,套路只是提供可能的思考方向(

看到题目,我们可以先从比较暴力的做法出发。如果区间 \([l_1, r_1]\) 和区间 \([l_2, r_2]\) 完全相同,说明对于所有满足 \(0 \leq k \leq r_1 - l_1 + 1\)\(s_{l1 + k} = s_{l2 + k}\) 。于是我们可以把区间相等转化成单点相等。由此得到暴力的思路:用至多 \(n\) 个并查集维护所有相同的单点,将 \(s\) 值相同的单点都合并在同一个并查集内。根据题目限制,十进制数的最高位不能是 \(0\) ,有 \(1\)\(9\)\(9\) 种选择;其余数位有 \(0\)\(9\) 十种选择。令 \(t\) 等于最终的并查集个数,合法的构造方案数为 \(9 \times 10^{t - 1}\)

这样的算法可以拿到 \(30\ pts\) ,但是如果数据强度够大,例如没有单点 \(s\) 值相等的情况,这种暴力的并查集就会被卡。因此,我们需要优化这个思路。考虑到数据范围,最好能将时间复杂度优化到 \(O(nlogn)\)

想要优化一个 \(log\) ,我们联想到与二进制有关的算法。我们可以使用类似于多重背包二进制优化的思想,将一个以 \(l\) 为左端点且长度为 \(2^p\) 的区间拆成不超过 \(\log_2^{r - l + 1}\) 个小区间。我们可以将并查集减少到 \(x\) 层,第 \(i\) 层维护长度为 \(2^i\) 的区间。换言之,我们用左端点来代表一个区间,再将一个区间抽象成一个结点。相同长度的区间在同一层内,构成可以被并查集维护的结构。

于是,我们可以考虑处理限制了。实际上,令 \(q = log_2^{r_1 - l_1 + 1}\)我们可以将区间 \([l_1, r_1]\) 拆分成两个分别以 \(l_1\)\(r_1 - 2^q + 1\) 开头的长度为 \(2^q\) 的区间,类似于 \(RMQ\) 问题。接着,我们在第 \(q\) 层将对应的结点所在的并查集合并,即合并 \([l_1, l_1 + 2^q - 1]\)\([l_2, l_2 + 2^q - 1]\) 对应的结点,再合并 \([r_1 - 2^q + 1, r_1]\)\([r_2 - 2^q + 1, r_2]\) 对应的结点。

这样做可以快速处理限制,但是会导致限制关系没有影响到所有 \(x\) 层的结点。我们需要将高层的限制关系转移到低层,最后用最底层的信息统计答案。因为这道题的限制关系表示两值相等,根据等式的传递性,我们可以判断这个模型具有可叠加性。因此,我们可以按区间长度从大到小枚举区间。假设我们需要将第 \(k\) 层的信息转移到第 \(k - 1\) 层,因为并查集中的区间一定和并查集的根区间相等,所以我们可以再次运用 \(RMQ\) 的处理方法,将两个区间拆成类似于长度为 \(2^{k - 1}\) 的上段提到的形式,再在第 \(k - 1\) 层将它们所在的并查集合并。最终的并查集个数为满足 \(f_{i, 0} = i\)\(1 \leq i \leq n\)\(i\) 的个数。按照上文提及的公式直接计算结果即可。

注意题目需要取模,注意精度问题。

参考代码

#include <cstdio>
using namespace std;

const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;

int n, m, k, cnt;
int f[maxn][25];
long long ans;

int get_log(int x) {
	int cnt = 0, num = 1;
	while (num <= x) {
		num *= 2;
		cnt++;
	}
	return cnt - 1;
}

int get(int x, int k) {
	if (f[x][k] == x) {
		return x;
	}
	return f[x][k] = get(f[x][k], k);
}

void merge(int x, int y, int k) {
	x = get(x, k);
	y = get(y, k);
	if (x != y) {
		f[x][k] = y;
	}
}

int main() {
//	freopen("P3295_1.in", "r", stdin);
	int l1, r1, l2, r2;
	scanf("%d%d", &n, &m);
	k = get_log(n);
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j <= k; j++) {
			f[i][j] = i;
		}
	} 
	for (int i = 1; i <= m; i++) {
		scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
		for (int j = k; j >= 0; j--) {
			if (l1 + (1 << j) - 1 <= r1) {
				merge(l1, l2, j);
				l1 += (1 << j);
				l2 += (1 << j);
			}
		}
	}
	for (int j = k; j; j--) {
		for (int i = 1; i + (1 << j) - 1 <= n; i++) {
			int p = get(i, j);
			merge(i, p, j - 1);
			merge(i + (1 << (j - 1)), p + (1 << (j - 1)), j - 1);
		}
	}
	for (int i = 1; i <= n; i++) {
		if (f[i][0] == i) {
			if (!ans) {
				ans = 9;
			} else {
				ans = (ans * 10) % mod;
			}
		}
	}
	printf("%lld\n", ans);
	return 0;
}
posted @ 2021-07-24 23:42  kymru  阅读(57)  评论(0编辑  收藏  举报