嘘~ 正在从服务器偷取页面 . . .

洛谷P3295 [SCOI2016] 萌萌哒 题解


洛谷P3295 [SCOI2016] 萌萌哒 题解

题目链接:P3295 [SCOI2016] 萌萌哒

题意

一个长度为 $n$ 的大数,用 $S_1S_2S_3 \cdots S_n$表示,其中 $S_i$ 表示数的第 $i$ 位, $S_1$ 是数的最高位。

告诉你一些限制条件,每个条件表示为四个数 $l_1,r_1,l_2,r_2$ ,即两个长度相同的区间,表示子串 $S_{l_1}S_{l_1+1}S_{l_1+2} \cdots S_{r_1}$ 与 $S_{l_2}S_{l_2+1}S_{l_2+2} \cdots S_{r_2}$ 完全相同。

比如 $n=6$ 时,某限制条件 $l_1=1,r_1=3,l_2=4,r_2=6$ ,那么 $123123$,$351351$ 均满足条件,但是 $12012$,$131141$ 不满足条件,前者数的长度不为 $6$ ,后者第二位与第五位不同。问满足以上所有条件的数有多少个。

输入格式

第一行两个数 $n$ 和 $m$,分别表示大数的长度,以及限制条件的个数。

接下来 $m$ 行,对于第 $i$ 行,有 $4$ 个数 $l_{i_1},r_{i_1},{l_{i_2}},r_{i_2}$,分别表示该限制条件对应的两个区间。

输出格式

一个数,表示满足所有条件且长度为 $n$ 的大数的个数。

答案可能很大,因此输出答案模 $ 10^9+7 $ 的结果即可。

数据范围

$1\le n\le 10^5,~1\le m\le 10^5,~1\le l_{i_1},r_{i_1},{l_{i_2}},r_{i_2}\le n$ ,并且保证 $r_{i_1}-l_{i_1}=r_{i_2}-l_{i_2}$ 。

首先相同的位置可以用并查集维护,这样就是 $\mathcal{O}(n^2)$ 的。

由于并查集是有结合律的,而我们每次都在合并两条等长的线段,所以考虑倍增。

设 $f(i,j)$ 表示 $[j, j + 2^i - 1]$ 的等价类集合。相当于给一个点开了 $i$ 个并查集以维护各个长度的线段。

对于每个询问考虑合并 $f(k,l)$ 和 $f(k, r - 2^k + 1)$ ,其中 $k= \log_2(r-l+1)$

不过因为我们只合并了区间的最上层,我们还需要 push_down 合并的结果。

比如 $f(i,j)=x$ ,那么把 $f(i-1,j)$ 和 $f(i-1,x)$ 合并,$f(i-1,j + 2^{i-1})$ 和 $f(i-1,x+2^{i-1})$ 合并即可

可以证明,每次只合并上层最后下放不会产生冲突,于是我们可以询问完再下放标记。(并查集的结合律)

一边操作一边下放好像也行,不过这样复杂度就变成了 $\mathcal{O}((n + m) \log n\cdot \alpha(n))$ ,没意义。

最后答案就是 $9\times 10^{T-1}$ ,其中 $T$ 表示 $f(0,j)$ 的不同等价类的个数,乘 $9$ 是因为不能有前导零。

因此时间复杂度 $\mathcal{O}(n \log n \cdot \alpha(n) + m \alpha(n))$

代码:

#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
const int mod = 1e9 + 7;
typedef long long ll;
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define N ((int)(1e5 + 15))

int lg[N], fa[19][N], sz[19][N]; char vis[N];
int find(int k, int x)
{
    if(fa[k][x] == x) return x;
    return fa[k][x] = find(k, fa[k][x]);
}
ll qpow(int b, ll a = 10)
{
    ll r = 1;
    for(; b; b >>= 1, a = a * a % mod)
        if(b & 1) r = r * a % mod;
    return r;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    int n, m; cin >> n >> m;
    for(int i = 2; i <= N - 5; i++) lg[i] = lg[i >> 1] + 1;
    for(int i = 0; i <= lg[n]; i++)
        for(int j = 1; j + (1 << i) - 1 <= n; j++) { fa[i][j] = j, sz[i][j] = 1; }
    for(int i = 1, l1, r1, l2, r2; i <= m; i++)
    {
        cin >> l1 >> r1 >> l2 >> r2;
        int k = lg[r1 - l1 + 1];
        int a = find(k, l1), b = find(k, l2);
        if(sz[k][a] > sz[k][b]) { swap(a, b); } sz[k][b] += sz[k][a]; fa[k][a] = b;
        int c = find(k, r1 - (1 << k) + 1);
        int d = find(k, r2 - (1 << k) + 1);
        if(sz[k][c] > sz[k][d]) { swap(a, b); } sz[k][d] += sz[k][c]; fa[k][c] = d;
    }
    for(int i = lg[n]; i; i--)
    {
        for(int j = 1; j + (1 << i) - 1 <= n; j++)
        {
            int f = find(i, j); if(f == j) continue;
            int a = find(i - 1, j), b = find(i - 1, f);
            if(sz[i - 1][a] > sz[i - 1][b]) { swap(a, b); } sz[i - 1][b] += sz[i - 1][a]; fa[i - 1][a] = b;
            int c = find(i - 1, j + (1 << (i - 1)));
            int d = find(i - 1, f + (1 << (i - 1)));
            if(sz[i - 1][c] > sz[i - 1][d]) { swap(c, d); } sz[i - 1][d] += sz[i - 1][c]; fa[i - 1][c] = d;
        }
    }
    int cnt = 0;
    for(int i = 1; i <= n; i++)
    {
        int f = find(0, i);
        if(!vis[f]) { vis[f] = true; ++cnt; }
    }
    cout << 9ll * qpow(cnt - 1) % mod << '\n';
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/article/u28ifrek


题外话

冰茶姬。冰茶姬。

话说大伙怎么都不写按秩合并啊。


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录