看上去毫无思路感觉不可做,可以先考虑暴力。
不难发现如果将整个区间一起考虑十分不好做,我们可以考虑对每一位进行限制,于是对于每个限制,我们将要求相同的每个位置加入并查集,那么最终答案就之和连通块个数有关了。
下面考虑优化这个暴力的过程,可以发现单独考虑每一位很好做是因为这些需要限制的区间是已知且能表示出来的,那么什么东西能以优秀复杂度表示一段区间且能预处理呢?没错,就是倍增。具体来说,对于每一条限制,我们从 \(l1, l2\) 开始倍增地跳,将倍增长度相同的线段在并查集中合并,而在最后查询的时候我们依然需要将限制放到每一位去,可以考虑将倍增长度为 \(2 ^ i\) 的线段往下拆分成 \(2 ^ {i - 1}\) 的线段,只需将该长度连通块内左半边合并,右半边合并即可。为了不让线段之间两两合并复杂度爆炸,我们只需考虑拆分一条线段和它在并查集上的父亲并合并即可。
从这题可以看出来倍增使用条件有下面几条:
-
没有修改,只有查询。
-
信息满足可减,可加性。
因此在没有修改情况下,我们一般可以考虑使用倍增。
#include<bits/stdc++.h>
using namespace std;
#define N 2000000 + 5
#define Mod 1000000007
#define rep(i, l, r) for(int i = l; i <= r; ++i)
#define dep(i, l, r) for(int i = r; i >= l; --i)
int n, m, l1, r1, l2, r2, cnt, fa[N];
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int c(int x, int y){
return x + y * n;
}
int Inc(int a, int b){
return (a += b) >= Mod ? a - Mod : a;
}
int Mul(int a, int b){
return 1ll * a * b % Mod;
}
int Qpow(int a, int b){
int ans = 1;
while(b){
if(b & 1) ans = Mul(ans, a);
a = Mul(a, a), b >>= 1;
}
return ans;
}
int find(int x){
return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
}
void Merge(int x, int y){
int a = find(x), b = find(y);
if(a == b) return;
fa[a] = b;
}
void solve(int l1, int r1, int l2, int r2){
int x = l1, y = l2;
dep(i, 0, 17) if(x + (1 << i) - 1 <= r1){
Merge(c(x, i), c(y, i));
x = x + (1 << i), y = y + (1 << i);
}
}
int main(){
n = read(), m = read();
rep(j, 0, 17) rep(i, 1, n) fa[c(i, j)] = c(i, j);
rep(i, 1, m){
l1 = read(), r1 = read(), l2 = read(), r2 = read();
solve(l1, r1, l2, r2);
}
dep(j, 1, 17) rep(i, 1, n) if(fa[c(i, j)] != c(i, j)){
int x = i, y = fa[c(i, j)] % n == 0 ? n : fa[c(i, j)] % n;
Merge(c(x, j - 1), c(y, j - 1));
if(x + (1 << (j - 1)) <= n && y + (1 << (j - 1)) <= n)
Merge(c(x + (1 << (j - 1)), j - 1), c(y + (1 << (j - 1)), j - 1));
}
rep(i, 1, n) if(fa[i] == i) ++cnt;
printf("%d", Mul(9, Qpow(10, cnt - 1)));
return 0;
}