bzoj4569: [Scoi2016]萌萌哒 并查集+倍增

bzoj4569: [Scoi2016]萌萌哒

Description

一个长度为n的大数,用S1S2S3…Sn表示,其中Si表示数的第i位,S1是数的最高位,告诉你一些限制条件,每个条
件表示为四个数,l1,r1,l2,r2,即两个长度相同的区间,表示子串Sl1Sl1+1Sl1+2…Sr1与Sl2Sl2+1Sl2+2…S
r2完全相同。比如n=6时,某限制条件l1=1,r1=3,l2=4,r2=6,那么123123,351351均满足条件,但是12012,13
1141不满足条件,前者数的长度不为6,后者第二位与第五位不同。问满足以上所有条件的数有多少个。

Input

第一行两个数n和m,分别表示大数的长度,以及限制条件的个数。接下来m行,对于第i行,有4个数li1,ri1,li2
,ri2,分别表示该限制条件对应的两个区间。
1≤n≤10^5,1≤m≤10^5,1≤li1,ri1,li2,ri2≤n;并且保证ri1-li1=ri2-li2。

Output

一个数,表示满足所有条件且长度为n的大数的个数,答案可能很大,因此输出答案模10^9+7的结果即可。

Sample Input

4 2
1 2 3 4
3 3 3 3

Sample Output

90

分析

首先这题本质上是一个并查集,但是一个一个合并显然爆炸。
考虑线段树,发现这个和线段树优化建边不太一样,是两个区间对应数合并。
有一种神奇的方法是St表。
用类似St表的方法建立 O(logn) O ( l o g n ) 个并查集。
可以建立一张图。每一层的节点都像下一层的两个节点连边,表示它的子区间。
然后从区间长度大的集子一直处理到区间长度为1的集子。
如果当前集子有限制,把限制下放到两个子集里面。
下放的操作就是,把当前区间的两个子区间分别合并到并查集堆顶区间的两个子区间上。

代码

#include<cstdio>
const int N = 1e5 + 10, P = 1e9 + 7;
int ri() {
    char ch = getchar(); int x = 0;
    for(;ch < '0' || ch > '9'; ch = getchar()) ;
    for(;ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) - '0' + ch;
    return x;
}
int f[N * 30], lc[N * 30], rc[N * 30], id[30][N], lg[N], bin[30], n, tp;
void Init() {
    lg[0] = -1; for(int i = 1;i <= n; ++i) lg[i] = lg[i >> 1] + 1;
    bin[0] = 1; for(int i = 1;i <= 29; ++i) bin[i] = bin[i - 1] << 1;
    for(int i = 0;bin[i] <= n; ++i)
        for(int j = 1;j + bin[i] - 1 <= n; ++j) {
            id[i][j] = ++tp;
            if(i) lc[tp] = id[i - 1][j], rc[tp] = id[i - 1][j + bin[i - 1]];
        }
}
int fi(int x) {return !f[x] ? x : f[x] = fi(f[x]);}
void Merge(int u, int v) {if(fi(u) != fi(v)) f[fi(u)] = fi(v);}
int main() {
    n = ri(); if(n == 1) return 0 * puts("10"); Init();
    for(int m = ri(); m--;) {
        int l1 = ri(), r1 = ri(), l2 = ri(), r2 = ri();
        int t = lg[r1 - l1 + 1];
        Merge(id[t][l1], id[t][l2]);
        Merge(id[t][r1 - bin[t] + 1], id[t][r2 - bin[t] + 1]);
    }
    for(int i = tp, t; i; --i)
        if(f[i]) Merge(lc[i], lc[t = fi(i)]), Merge(rc[i], rc[t]);
    int cnt = 0; for(int i = 1;i <= n; ++i) if(!f[i]) ++cnt;
    int ans = 9; for(;--cnt;) ans = (1LL * ans * 10) % P;
    printf("%d\n", ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值