【Luogu P3295】[SCOI2016]萌萌哒

链接:

洛谷

题目大意:

在一个 \(n\) 位数字中有 \(m\) 段长度相同的区间内容相同,求这个数的方案数。

正文:

相同的就只能算一次,所以就用并查集维护,太暴力的 \(\mathcal{O}(nm)\) 的纸飞机飞不远,但优化是自由,明天换一个倍增算法,后天就有了 Accepted,这就是信息学精神。

\(f_{i,j}\) 表示 \([i,i+2^j-1]\) 的区间的根的左端点。先 \(\mathcal{O}(m\log n)\) 保留各个区间的并查集关系,再 \(\mathcal{O}(n\log^2n)\) 下传。

代码:

const int N = 1e5 + 10, M = 30;

inline ll Read() {
	ll x = 0, f = 1;
	char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') f = -f, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
	return x * f;
}

int n, m, t;
int f[N][M];
ll ans;
int Find (int x, int k) { return f[x][k] == x? x: f[x][k] = Find(f[x][k], k); }
void Merge (int x, int y, int k) {
	int u = Find(x, k), v = Find(y, k);
	if (u != v) f[u][k] = v;
	return;
}

int main() {
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	n = Read(), m = Read();
	t = log2(n);
	for (int i = 1; i <= n; i++) 
		for (int j = 0; j <= t; j++) 
			f[i][j] = i;
	for (int i = 1; i <= m; i++) {
		int al = Read(), ar = Read(), bl = Read(), br = Read();
		for (int j = t; ~j; j--)
			if (al + (1 << j) - 1 <= ar) Merge(al, bl, j), al += (1 << j), bl += (1 << j);
	}
	for (int j = t; j; j--)
		for (int i = 1; i + (1 << j) - 1 <= n; i++)
		{
			int pos = Find (i, j);
			Merge (i, pos, j - 1), Merge (i + (1 << j - 1), pos + (1 << j - 1), j - 1);
		}
	for (int i = 1; i <= n; i++)
		if (f[i][0] == i) ans = !ans? 9: ans * 10 % 1000000007;
	printf ("%lld\n", ans);
	return 0;
}
posted @ 2021-11-18 19:01  Jayun  阅读(18)  评论(0编辑  收藏  举报