【题解】CF1530G What a Reversal

CF1530G What a Reversal 题解

题意简述

有两个长为 \(n\)\(01\)\(S\)\(T\),你可以进行至多 \(4n\) 次操作,每次翻转(注意不是反转)\(S\) 的一个恰有 \(k\)\(1\) 的子串,目标串是 \(T\)。判断无解或者构造方案。\(\sum n \leq 2000\)

题解

这个题太神了啊……想了好多天才想出来。(为啥小 t 的构造题都这么难

首先 \(k=0\) 或者 \(k \geq n\) 的情况是 trivial 的,特判一下就好了。接下来说 \(1 \leq k < n\) 的情况。

如果 \(S\)\(T\)\(1\) 的个数不一样我们也想都不用想。

结论:操作是可逆的。

这个结论的正确性是显然的。这意味着我们只要把 \(S\)​ 和 \(T\)​ 都通过某一操作变成同一个串就好了。接下来做的事情目标都是把 \(S,T\)\(2n\) 步之内变成同一个串。

然后我们考虑 \(S\) 作为 \(01\) 串的形式不是很容易操作,进行一步转化:

\(p_i\) 表示 \(S\) 中第 \(i\)\(1\) 之后有几个连续的 \(0\)。比如 \(S = 1001100011\) 可以表示成 \(p = [0, 2, 0, 3, 0, 0]\)

\(m\) 表示 \(S\) 中有几个 \(1\)\(a_i\) 表示第 \(i\)\(1\)\(S\) 中的位置。令 \(a_0 = 0\)\(a_{m + 1} = n + 1\)

结论:翻转 \(S\) 的一个包含第 \(l\) 个到第 \(r\)\(1\) 的合法子串,相当于把 \(p_{l..r-1}\) 翻转,并且把 \(p_{l-1}\)\(p_r\) 变成 \(p_{l-1}'\)\(p_r'\) 满足 \(p_{l-1}' + p_r' = p_{l-1}+p_{r}\)

这个结论的正确性也是显然的。

我们对于每一个 \(i > k\) 并且 \(p_i > 0\)\(i\) 进行操作:翻转 \([a_{i - k + 1},a_{i + 1} - 1]\) 这个区间。这个操作做了什么?

直观上考虑,这个操作使得最后若干位都是 \(1\),把原本在后面的一段 \(0\) 放到前面去了。

从对 \(p\) 的贡献考虑,这个操作翻转了 \(p_{i - k + 1 .. i}\),并且把 \(p_{i + 1}\) 变成 \(0\)\(p_{i - k}\) 变成 \(p_{i - k} + p_{i + 1}\)。相当于把后面的 \(p\) 加到前面去了。

我们反复执行这个操作,就能使得所有的 \(0\)​ 都在第 \(k\)​ 个 \(1\)​ 之前,即 \(\forall i > k, p_i = 0\)​。

这个至多会使用 \(m-k\) 个操作。

然后考虑处理接下来的情况。如果我们翻转 \(p_{1..k-1}\)​​​ 和 \(p_{2..k}\)​​​,则显然 \(p_1, p_2, p_3, \ldots, p_{k-2},p_{k-1}, p_k\)​​​ 就会变成 \(p_{k-1}, p_k, p_1, p_2, \ldots, p_{k-2}\)​​​。

我们考虑在翻转 \(p_{1..k-1}\) 的时候把 \(p_k\) 全部加到 \(p_0\) 上面去,即翻转区间 \([a_1, a_{k+1}-1]\)。这样我们可以把 \(p_k\) 变成 \(0\)

容易发现 \(p_{1..k}\) 发生了一个长度为 \(2\)​ 的循环移位。

Case 1

如果 \(k\) 是奇数,那么在 \(k\) 次移位之内 \(p_1, p_2, \ldots, p_k\) 中的每一个数都肯定到达过一次 \(p_k\)​ 的位置。(在每次操作时 \(p_k\) 位置上面的数分别是:\(p_k, p_{k-2}, p_{k-4}, p_{k-6}, \ldots, p_1, p_{k-1}, p_{k-3}, \ldots, p_2\)。)

因为每次移位把 \(p_k\) 位置上的数清零了,所以 \(k\) 次移位一定能够使 \(p_{1..k}\) 的数都是 \(0\)\(p_0\) 的值为 \(\sum _{i=1} ^k p_i\)​ 即 \(m\)

所以我们可以在至多 \(2k + m - k = m + k \leq 2n\) 次操作内把 \(S\) 变成 \(0000..0011111\) 这种形式。(\(0\) 全部在 \(1\) 的前面)我们对于 \(T\) 进行同样的操作再把顺序倒过来就行了。

Case 2

如果 \(k\) 是偶数的话,我们明显并不能把所有的 \(p_i\) 都加到 \(p_0\) 上面。因为我们无论如何操作,循环移位的话总是有一半的位置不能到达 \(p_k\)

为什么呢?

因为奇数总是管奇数,偶数总是管偶数。

那你不是把奇数都放到一个数,把偶数放到一个数就好了吗?

有道理啊/qd/qd/qd/qd/qd

结论:如果 \(k\)​ 是偶数,则 \(\sum_{x\text{ is even}} p_x\)​ 和 \(\sum _{x \text{ is odd}} p_x\) 是一个定值。

因为奇数总是管奇数,偶数总是管偶数。

那你就把奇数都放到一个数,把偶数放到一个数就好了。

具体的,我们在进行翻转 \(p_{1..k}\) 的时候把 \(p_k\) 加到 \(p_0\) 上面,在翻转 \(p_{2..k+1}\) 的时候把 \(p_1\) 加到 \(p_{k+1}\) 上。这样,在 \(k\) 次循环移位之后,我们一定可以把奇数位的都加到 \(p_{k+1}\),偶数位的都加到 \(p_0\)

所以我们可以至多 \(m - k + 2k = m + k \leq 2n\) 次操作内把 \(S\) 变成 \(000..00111111111100..01111111\) 这种形式(里面第一个连续 \(1\) 段一定是 \(k\)\(1\))。

实现有点细节,不知道为什么有些人写的很简单。

#include <bits/stdc++.h>

const int N = 2005;

int n, k, m, p[N], q[N];
char s[N], t[N];
std::vector<std::pair<int, int>> r1, r2;
// p[] & q[] describes the p array of string s & t.
// r1, r2 describes the steps that can make s & t the same intended string.

int s_eq_t() { for (int i = 1; i <= n; ++i) if (s[i] != t[i]) return 0; return 1; }
// s_eq_t() checks if s & t is the same string.

int get_k_eq_m() {
	for (int i = 1 - n; i < n; ++i) {
		int flg = 1;
		for (int j = 1; j <= n; ++j)
			if (s[j] != ((i + j > 0 && i + j <= n) ? t[i + j] : 0)) { flg = 0; break; }
		if (flg) return i;
	}
	return n;
}
// get_k_eq_m() checks if s is a cyclic shift of t, and returns the steps if it is.

void sol_k_eq_m() {
	int st, ed;
	for (int i = 1; i <= n; ++i) if (s[i]) { st = i; break; }
	for (int i = n; i; --i) if (s[i]) { ed = i; break; }
	int k = get_k_eq_m(); // checks if s is a cyclic shift of t.
	if (k < n) {
		puts("2");
		printf("%d %d\n", std::min(st, st + k), std::max(ed, ed + k));
		printf("%d %d\n", st + k, ed + k);
		return;
	}
	std::reverse(t + 1, t + n + 1);
	k = get_k_eq_m(); // checks if s is a cyclic shift of t's reverse.
	if (k < n) {
		puts("3");
		printf("%d %d\n", std::min(st, st + k), std::max(ed, ed + k));
		printf("%d %d\n", st + k, ed + k);
		printf("1 %d\n", n);
		return;
	}
	puts("-1"); // if not, no solution
}
// sol_k_eq_m() solves the case of k equals m.

void get_p(char *s, int *p) {
	int lst = 0, cnt = 0;
	for (int i = 1; i <= n; ++i)
		if (s[i]) p[cnt++] = i - lst - 1, lst = i;
	p[cnt] = n - lst;
}
// get_p() calculates the array p according to s.

void sol(int *p, std::vector<std::pair<int, int>> &r) {
	auto get_pos = [&](int x)->int {
		if (x == 0) return 0;
		if (x == m + 1) return n + 1;
		int res = 0;
		for (int i = 0; i < x; ++i) res += p[i];
		return res + x;
	}; // get the position of the xth 1 according to p[].
	for (int i = m; i > k; --i)
		if (p[i]) {
			int st = get_pos(i - k + 1);
			int ed = get_pos(i + 1) - 1;
			if (st != ed) r.emplace_back(st, ed);
			p[i - k] += p[i], p[i] = 0;
			std::reverse(p + i - k + 1, p + i);
		} // if i > k and p[i] has a value, add p[i] to p[i - k]. 
	// This operation makes p[i] = 0 for all i > k.
	for (int t = 0; t < k; ++t) {
		int st = get_pos(1);
		int ed = get_pos(k + 1);
		if (st != ed - 1) r.emplace_back(st, ed - 1);
		p[0] += p[k], p[k] = 0;
		std::reverse(p + 1, p + k);
		st = get_pos(2);
		ed = get_pos(k + 1);
		if (st != ed) r.emplace_back(st, ed);
		std::reverse(p + 2, p + k + 1);
	} // do k cyclic shifts of [1..k] such that all numbers has reached the position p[k] once. add p[k] to p[0].
	// This operation makes p[i] = 0 for all i in [1, k].
	// After this, p[i] = 0 stands for all i > 0.
	// Therefore, applying sol() to both s and t makes them the same string with all 0s at the front of the string. 
}
// sol() solves the case k is odd.

void sol2(int *p, std::vector<std::pair<int, int>> &r) {
	auto get_pos = [&](int x) {
		if (x == 0) return 0;
		if (x == m + 1) return n + 1;
		int res = 0;
		for (int i = 0; i < x; ++i) res += p[i];
		return res + x;
	}; // get the position of the xth 1 according to p[].
	for (int i = m; i > k; --i)
		if (p[i]) {
			int st = get_pos(i - k + 1);
			int ed = get_pos(i + 1) - 1;
			if (st != ed) r.emplace_back(st, ed);
			p[i - k] += p[i], p[i] = 0;
			std::reverse(p + i - k + 1, p + i);
		} // if i > k and p[i] has a value, add p[i] to p[i - k]. 
	// This operation makes p[i] = 0 for all i > k. (same as the one in sol())
	for (int t = 0; t < k; ++t) {
		int st = get_pos(1);
		int ed = get_pos(k + 1);
		if (st != ed - 1) r.emplace_back(st, ed - 1);
		p[0] += p[k], p[k] = 0;
		std::reverse(p + 1, p + k);
		st = get_pos(1);
		ed = get_pos(k + 1);
		p[k + 1] += p[1], p[1] = 0;
		if (st + 1 != ed) r.emplace_back(st + 1, ed);
		std::reverse(p + 2, p + k + 1);
	} // do k cyclic shifts of [1..k] such that all numbers has reached the position p[k] or p[1] once.
	// This operation makes p[i] = 0 for all i in [1, k].
	// After this, p[i] = 0 stands for all i in [1, k] \cup [k + 2, m]
	// Therefore, applying sol() to both s and t makes them the same string 000..0001111..11(k 1s)000.0000111.11
}
// sol2() solves the case k is even.

int main() {
	int T; scanf("%d", &T);
	while (T--) {
		scanf("%d%d%s%s", &n, &k, s + 1, t + 1);
		int cnts = 0, cntt = 0;
		for (int i = 1; i <= n; ++i) s[i] -= '0', t[i] -= '0';
		for (int i = 1; i <= n; ++i) cnts += s[i], cntt += t[i];
		if (cnts != cntt) { puts("-1"); continue; } else m = cnts;
		if (s_eq_t()) { puts("0"); continue; }
		if (k == 0 || k > m) { puts("-1"); continue; }
		if (k == m) { sol_k_eq_m(); continue; }
		get_p(s, p);
		get_p(t, q);
		r1.clear(), r2.clear();
		if (k & 1) {
			sol(p, r1);
			sol(q, r2);
		} else {
			int tmp[2][2]; 
			memset(tmp, 0, sizeof(tmp));
			for (int i = 0; i <= m; ++i) tmp[0][i & 1] += p[i];
			for (int i = 0; i <= m; ++i) tmp[1][i & 1] += q[i];
			if (tmp[0][0] != tmp[1][0] || tmp[0][1] != tmp[1][1]) { puts("-1"); continue; }
			sol2(p, r1);
			sol2(q, r2);
		}
		printf("%d\n", (int)r1.size() + (int)r2.size());
		for (unsigned i = 0; i < r1.size(); ++i) printf("%d %d\n", r1[i].first, r1[i].second);
		for (unsigned i = r2.size() - 1; ~i; --i) printf("%d %d\n", r2[i].first, r2[i].second); // output in reverse order
	}
	return 0;
}
posted @ 2021-12-16 07:54  zghtyarecrenj  阅读(39)  评论(0编辑  收藏  举报