洛谷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
题外话:
冰茶姬。冰茶姬。
话说大伙怎么都不写按秩合并啊。