题面
有一个长度为\(n\)的0/1
串,有些位置未知。给你\(m\)组条件,第\(i\)组表示\(s[a_i, a_i + l_i - 1]=s[b_i, b_i + l_i - 1]\)。输出满足条件的字符串中字典序最小的。(保证存在)
\(n,m\le 10^6\)
题解
考虑暴力把每个条件的相对位置用并查集并起来。最后看一下每砣相同的字符可以取什么即可。
复杂度\(O(nm\alpha(n))\),炸得妥妥的。
把每个条件拆成长度形为\(2^k\)的几个条件,插入到\(\log n\)个并查集里。第\(k\)个并查集中的条件长度都是\(2^k\)。显然,每个并查集再怎么搞,它的边数也只有\(O(n)\)条。从大到小枚举\(k\),把第\(k+1\)个并查集中每一条标志\(s[a, a + 2^{k+1})=s[b, b + 2^{k+1})\)的边,拆成\(s[a, a + 2^k)=s[b, b + 2^k)\)和\(s[a+2^k, a + 2^{k+1})=s[b+2^k, b + 2^{k+1})\)两组条件,插入到第\(k\)个并查集中。最终编号\(0\)的并查集就是刚刚暴力求出的并查集。
复杂度降为\(O(m\log n\ \alpha(n))\),你活了。
代码:
#include <cstdio>
#include <cstring>
using namespace std;
#define N 1000100
char s[N];
int n, m;
int a[N], b[N], l[N], col[N]; // col[i]表示集合i必须取什么字符(0或1的ASCII)。若集合i中所有元素位置,其值为零。
struct Union_Find { // 并查集模板 按秩合并+路径压缩
int fa[N], rank[N];
void init()
{
for (int i = 1; i <= n; i++)
fa[i] = i;
for (int i = 1; i <= n; i++)
rank[i] = 0;
}
int getf(int x) {
return x == fa[x] ? x : fa[x] = getf(fa[x]);
}
inline void merge(int x, int y) {
if (x <= n && y <= n) {
x = getf(x);
y = getf(y);
if (x == y)
return;
if (rank[x] < rank[y])
fa[x] = y;
else if (rank[x] > rank[y])
fa[y] = x;
else {
fa[x] = y;
rank[y]++;
}
}
}
} set[2]; // 我滚了(?)
int main()
{
scanf("%s %d", s + 1, &m);
for (int i = 1; i <= m; i++)
scanf("%d %d %d", a + i, b + i, l + i);
n = strlen(s + 1);
set[0].init();
for (int i = 20; i >= 0; i--) {
int k = 1 << i, t = i & 1;
// 事实上,我这里是先把$k+1$推到$k$上,再加入原先拆出的限制的。
for (int j = 1; j <= m; j++)
if(l[j] >= k) { // 拆限制
set[t].merge(a[j], b[j]);
a[j] += k;
b[j] += k;
l[j] -= k;
}
if (i == 0) // i = 0 不用向下推
continue;
set[t ^ 1].init();
for (int j = 1; j <= n; j++) { // 向下推
set[t ^ 1].merge(j, set[t].fa[j]);
set[t ^ 1].merge(j + (k >> 1), set[t].fa[j] + (k >> 1));
}
}
for (int i = 1; i <= n; i++)
if (s[i] != '?')
col[set[0].getf(i)] = s[i]; // i所在集合必须取什么字符?
for (int i = 1; i <= n; i++) {
if (s[i] != '?')
putchar(s[i]);
else{
if (!col[set[0].getf(i)]) // 若这个位置的字符根据条件也无法确定
col[set[0].getf(i)] = '0'; // 按字典序贪心,则此处应填'0'。
putchar(col[set[0].getf(i)]);
}
}
}