bzoj 4569 [Scoi2016]萌萌哒 并查集 + ST表

时间:2021-12-17 15:37:04

题目链接

Description

一个长度为\(n\)的大数,用\(S_1S_2S_3...S_n\)表示,其中\(S_i\)表示数的第\(i\)位,\(S_1\)是数的最高位,告诉你一些限制条件,每个条件表示为四个数,\(l_1,r_1,l_2,r_2\),即两个长度相同的区间,表示子串\(S_{l1}S_{l1+1}S_{l1+2}...S_{r1}\)与\(S_{l2}S_{l2+1}S_{l2+2}...S_{r2}\)完全相同。

问满足以上所有条件的数有多少个。

思路

参考

https://www.cnblogs.com/owenyu/p/7146428.html

暴力想法

对于每一个条件,将\([l_1,r_1]\)与\([l_2,r_2]\)范围内对应的数一一\(union\)起来,最后检查有多少个集合\(cnt\),答案即为\(10^{cnt-1}*9\).

优化

运用\(ST\)表的思路,但形式与之相反。

一般的\(ST\)表是从小区间中衍伸得到大区间的性质,本题则是从大区间向小区间推。

开\(logn\)个并查集,对于每一个条件,拆成前后两部分分别进行\(union\);

最后从上往下推,将每层的每个大区间拆成下一层的两个小区间。

最后检查最底层。

Code

#include <bits/stdc++.h>
#define F(i, a, b) for (int i = (a); i < (b); ++i)
#define F2(i, a, b) for (int i = (a); i <= (b); ++i)
#define dF(i, a, b) for (int i = (a); i > (b); --i)
#define dF2(i, a, b) for (int i = (a); i >= (b); --i)
#define maxn 100000
#define maxk 17
using namespace std;
typedef long long LL;
bool vis[maxn+10];
int sz[maxk+1][maxn+10], fa[maxk+1][maxn+10], Log[maxn+10], bi[maxk+1], n, m;
inline int find(int dep, int x) {
return fa[dep][x]==x ? x : fa[dep][x]=find(dep, fa[dep][x]);
}
inline void unionn(int dep, int x, int y) {
x = find(dep, x), y = find(dep, y);
if (x==y) return;
if (sz[dep][x]>sz[dep][y]) swap(x, y);
fa[dep][x] = y;
}
void init() {
Log[0] = -1; bi[0] = 1;
F2(i, 1, maxn) Log[i] = Log[i>>1]+1;
int k=Log[n];
F2(i, 1, maxk) {
bi[i] = bi[i-1]<<1;
}
F2(i, 0, maxk) F2(j, 1, n) fa[i][j]=j, sz[i][j]=1;
}
const LL mod=1e9+7;
LL poww(LL a, LL b) {
LL ret=1;
while (b) {
if (b&1) (ret*=a)%=mod;
(a*=a)%=mod;
b>>=1;
}
return ret;
}
int main() {
scanf("%d%d", &n,&m);
init();
F(i, 0, m) {
int l1, r1, l2, r2;
scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
int dep=Log[r1-l1+1];
unionn(dep, l1, l2);
unionn(dep, r1-bi[dep]+1, r2-bi[dep]+1);
}
dF(dep, maxk, 0) {
F2(i, 1, n) {
int x=find(dep, i);
unionn(dep-1, i, x);
unionn(dep-1, i+bi[dep-1], x+bi[dep-1]);
}
}
int ans=0;
F2(i, 1, n) ans += (find(0, i)==i);
printf("%lld\n", poww(10, ans-1)*9%mod);
return 0;
}