并查集+ST表(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;
}

浙公网安备 33010602011771号