并查集+ST表(P3295)

P3295链接

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}\),分别表示该限制条件对应的两个区间。

\(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}$ 。

输出格式

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

输入输出样例 #1

输入 #1

4 2
1 2 3 4
3 3 3 3

输出 #1

90

看题面,意思就是给定数字串长度n,接下来给m对数字,处于两个区间内的数字要一模一样

就是说只要考虑其中一个区间的可能性就行,然后除了第一个数字有9种可能外,其他数字都是10种可能

只取其中一个区间,并且只计算其中一个区间,可以用并查集将两区间合并,这样其中一个区间A的父就是另一个区间B,A内的数字都不参与计算,在统计答案时只在区间的父为自己时进行统计

因此为了统计答案,我们需要知道每个数字的父是谁,所以只对给出的区间进行并查集还不够,还要把父下放到区间内每个数字,由大区间缩小到小区间最后到单个数字,能想到什么?线段树和ST表!

但是线段树是为了能够随时修改数据,我们这里只需要查询就可以了,不需要费劲写线段树,用ST表就可

具体来说就是:

1:先初始化ST表,把每个区间的数值设置为区间左端点

2:开始读入要求的区间,对区间并查集

3:最后用二进制分解把ST表的信息从区间由大到小下放(如果二进制分解后一个区间的父不为自己的话),直到长度为1


Q:为什么是左端点?

A:因为这样子设置的话,最开始所有的长度为1的区间的左端点都是自己,处理完输入之后,如果一个区间的父不为自己的话,设这个区间的左端点为A,这个区间的父区间的左端点为B,那么就可以把这个A二进制分解后的区间的并到B二进制分解后的区间,简而言之,就是把最初的需要合并的区间二进制分解后再次合并,直到区间长度为1,可以理解为:两个区间先合并,再把两个区间的一半对应匹配,然后循环往复,直到长度为1.此时,我们就能去达成目的了:只统计区间的父为自己的

参考代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const long long int MOD = 1e9+7,N = 1e5+5;
int n,m,l1,l2,r1,r2,f[N][20];
LL ans;
int gf(int x,int k)
{
    if (f[x][k] != x) f[x][k] = gf(f[x][k],k);
    return f[x][k];
}

void merge(int x,int y,int k)
{
    int fx = gf(x,k),fy = gf(y,k);
    if(fx!=fy) f[fx][k] = fy;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n >> m ;
    int p = log2(n);
    for(int i=1;i<=n;i++)
    {
        for(int k=0;k<=p;k++)
        {
            f[i][k]=i;
        }
    }
    for(int i=1;i<=m;i++)
    {
        cin >> l1 >> r1 >> l2 >> r2;
        for(int k = p;k>=0;k--)
        {
            if(l1+(1<<k)-1<=r1)
            {
                merge(l1,l2,k);
                l1+=(1<<k);
                l2+=(1<<k);
            }
        }
    }
    for(int k=p;k>=1;k--)
    {
        for(int i=1;i+(1<<k)-1<=n;i++)
        {
            int pos = gf(i,k);
            merge(i,pos,k-1);
            merge(i+(1<<(k-1)),pos+(1<<(k-1)),k-1);
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(f[i][0]==i) ans = ans? (ans*10ll)%MOD : 9ll;
    }
    cout << ans << endl;

	return 0;
}
posted @ 2025-08-24 19:57  石磨豆浆  阅读(8)  评论(0)    收藏  举报