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

洛谷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 !
评论
  目录