【Luogu P3295】[SCOI2016]萌萌噠
阿新 • • 發佈:2021-11-18
連結:
題目大意:
在一個 \(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; }