1. 程式人生 > 其它 >【Luogu P3295】[SCOI2016]萌萌噠

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