BZOJ 4569 萌萌哒

本文介绍了一种使用并查集解决区间合并问题的方法,并通过一道具体的题目详细展示了如何利用倍增技巧优化区间合并的过程,最终实现高效求解。

题目传送门

4569: [Scoi2016]萌萌哒

Time Limit: 10 Sec Memory Limit: 256 MB

Submit: 483 Solved: 221

[Submit][Status][Discuss]

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

题解

这道题显然要用并查集,不过需要进行区间合并。所以我最开始选择了线段树····
在经历千辛万苦之后,我确定了线段树并不能搞定此题。
看此题数据,我们就蒙一个 O(nlog(n)) 级的算法。这样我们可以用f[i][j]来表示一段区间的父亲。
最暴力的方法就是表示i..j区间的父亲,但显然会炸。我们就联想到倍增,用f[i][j]表示i..i+(2^j)-1的区间父亲。
这样我们对于每一个区间的合并,我们最多进行 logn 次合并,在算上并查集的 O(α(a)) ,总的复杂度为 O(nlognα(a))

#include <cstdio>
//Input
char Inc; int Ina;
inline int geti() {
    while ((Inc = getchar()) < '0' || Inc > '9'); Ina = Inc - '0';
    while ((Inc = getchar()) >= '0' && Inc <= '9') Ina = (Ina << 3) + (Ina << 1) + Inc - '0'; return Ina;
}
//
const int Mod = 1e9 + 7, N = 100005;
int f[N][17]; bool vis[N];
int find(int i, int j) {
    return (f[i][j] ^ i) ? f[i][j] = find(f[i][j], j) : i;
}

void Union(int k, int i, int j) {
    if (find(i, k) ^ find(j, k)) {
        f[f[i][k]][k] = f[j][k];
        if (!k--) return;
        Union(k, i, j); Union(k, i + (1 << k), j + (1 << k));
    }
}

int main() {
    int n, m, i, j, a, b, c, d, ans;
    n = geti(), m = geti();
    if (n < 2) return puts("9"), 0;
    for (j = 0; (1 << j) <= n; ++j) 
      for (i = 1; i + (1 << j) - 1 <= n; ++i)
        f[i][j] = i;
    while (m--) {
        a = geti(), b = geti(), c = geti(), d = geti();
        j = b - a + 1, i = 0;
        while ((1 << i + 1) <= j) ++i;
        Union(i, a, c); Union(i, b - (1 << i) + 1, d - (1 << i) + 1);
    }
    ans = 9; vis[find(1, 0)] = true;
    for (i = 2; i <= n; ++i) if (!vis[find(i, 0)]) vis[find(i, 0)] = true, ans = 10LL * ans % Mod;
    printf("%d\n", ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值