题目大意:
考虑一个暴力,将需要相等的点放到一个并查集里,设最后并查集的个数是 $k$,则答案为 $9 \times 10^{k-1}$,复杂度 $O(nm)$。
正解是建一棵并查集树,第 $j$ 层第 $i$ 个节点表示区间 $[i,i+2^j]$,如下为合并区间 $[1,4]$ 和 $[5,8]$ 的情况,虚线箭头为代码注释中的操作 $1$,实线箭头为操作 $2$,合并过程详见代码。
#include <cstdio>
const int N = 100005, P = 1e9+7;
int fa[20][N], a[N], cnt;
int read() {
int x = 0; char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x;
}
int pow(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1LL * a * a % P)
if (b & 1) res = 1LL * res * a % P;
return res;
}
int find(int x, int y) {
if (fa[x][y] != y) fa[x][y] = find(x, fa[x][y]);
return fa[x][y];
}
void merge(int k, int x, int y) {
int fx = find(k, x), fy = find(k, y);
if (fx != fy) fa[k][fx] = fy;
}
int main() {
int n = read(), m = read();
for (int i = 0; 1 << i <= n; ++i)
for (int j = 1; j + (1 << i) - 1 <= n; ++j)
fa[i][j] = j;
for (int i = 2; i <= n; ++i) a[i] = a[i>>1] + 1; //计算层数
while (m--) { //操作1
int l1 = read(), r1 = read(), l2 = read(), r2 = read();
merge(a[r1-l1+1], l1, l2); //因为有可能r1-l1+1不是2的整数幂, 所以要合并两次
merge(a[r1-l1+1], r1 - (1 << a[r1-l1+1]) + 1, r2 - (1 << a[r1-l1+1]) + 1);
}
for (int i = a[n]; i; --i) { //操作2
for (int j = 1; j + (1 << i) - 1 <= n; ++j) {
merge(i - 1, j, find(i, j));
merge(i - 1, j + (1 << i - 1), find(i, j) + (1 << i - 1));
}
}
for (int i = 1; i <= n; ++i) if (find(0, i) == i) ++cnt;
printf("%lld\n", 9LL * pow(10, cnt - 1) % P);
return 0;
}
时间复杂度 $O(n \log n)$