BZOJ 4569 萌萌哒

Link

Solution

神了。 暴力并查集,最后看有多少个集合。设个数为xx,那么答案就是910x19*10^{x-1},然而明显很多次操作是重复的。 那就考虑区间一下?似乎并不好直接把并查集套到线段树上。 然后就开始高能: 维护倍增的并查集!fa[lg][pos]fa[lg][pos]表示pospos之后2lg2^{lg}长度的区间所属的集合,那么合并就可以拆成O(logn)O(\log n)段。 对于两个集合的合并,不停地向下递归合并长度更小的对应集合,直到某两个小区间已经被合并到了一个集合里面。 这种思想需要好好领会。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "lucida"
using std::swap;
const int P=1e9+7,MAXN=1e5+11,MAXLOG=20;
int log_2[MAXN];
struct _{_(){
log_2[0]=-1;
for(int i=1;i<MAXN;++i) {
log_2[i]=log_2[i>>1]+1;
}
}}Auto;
int fa[MAXLOG][MAXN];
int Root(int lg,int x) {
return !fa[lg][x]?x:fa[lg][x]=Root(lg,fa[lg][x]);
}
void Union(int lg,int x,int y) {
if(rand()&1) {
swap(x,y);
}
int rx=Root(lg,x),ry=Root(lg,y);
if(rx!=ry) {
fa[lg][rx]=ry;
if(lg) {
Union(lg-1,x,y);
Union(lg-1,x+(1<<(lg-1)),y+(1<<(lg-1)));
}
}
}
int main() {
freopen("input","r",stdin);
srand(0x1f1f1f1f);
int n,m;is>>n>>m;
for(int i=1;i<=m;++i) {
int l1,r1,l2,r2;is>>l1>>r1>>l2>>r2;
for(int len=r1-l1+1,lg=log_2[len];~lg;--lg) {
if(len>>lg&1) {
Union(lg,l1,l2);
l1+=1<<lg;
l2+=1<<lg;
}
}
}
int cnt=0;
for(int i=1;i<=n;++i) {
cnt+=fa[0][i]==0;
}
int64 Ans=9;
for(int i=1;i<=cnt-1;++i) {
(Ans*=10)%=P;
}
os<<Ans<<'\n';
return 0;
}