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