多校联考-string(个人认为比较有趣)

题面

有一个长度为\(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)]);
		}
	}
}

猜你喜欢

转载自www.cnblogs.com/skiceanAKacniu/p/13170040.html