[EC Final 2022] Binary String 题解
前言
题目链接:洛谷。
其他题解太抽象了。
题意简述
给定排列在环上的 \(01\) 串 \(S\),每一秒钟,每个 \(01\) 同时变为 \(10\)。求无限秒内会出现多少种不同的字符串。
题目分析
先来欣赏艺术,打表找规律。为方便观察,我们不妨用 @ 来代替字符 \(1\),空格 来代替字符 \(0\)。以下展示了一个 \(01\) 串在每一秒的形态。
1010111110001100000011111111111110101010101010100000111101010
@ @ @@@@@ @@ @@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@@ @ @
@ @ @@@@ @ @ @ @@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@@ @ @
@ @ @ @@@ @ @ @ @ @@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@@ @
@ @ @ @@@ @ @ @ @ @@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@@ @
@ @ @ @ @@@ @ @ @ @ @@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@@
@ @ @ @ @@ @ @ @ @ @ @@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@@
@ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@
@@ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@@@@@ @ @ @ @ @ @ @ @@
@@@ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@@@@ @ @ @ @ @ @ @ @ @
@@@@ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@@@ @ @ @ @ @ @ @ @ @
@@@@ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@@ @ @ @ @ @ @ @ @ @ @
@ @@@@ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @ @ @ @
@ @@@ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @ @ @ @
@ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @ @ @
@ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @ @ @
@ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @ @
@ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @ @
@ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @
@ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @
@ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @
.............................................................
@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @ @ @ @ @ @
@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @ @ @ @ @
@@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @ @ @ @ @
@ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @ @ @ @
@ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @@@@@@@@@ @ @ @ @ @ @ @ @ @ @
我们观察到:
- 最终字符串会原地转圈,陷入循环。
- 称长度大于一的连续一段 \(1\) 为「\(1\) 段」,连续一段 \(0\) 为「\(0\) 段」。对于 \(1\) 段,若其右侧是 \(01\),即右侧不是 \(0\) 段,则会在下一秒整体右移一个位置;对于 \(0\) 段类似,会整体左移一个位置。若 \(1\) 段右侧碰到了 \(0\) 段,两段交界处无变化,由于 \(1\) 段左端点还是右移一单位,\(0\) 段右端点还是左移一单位,所以导致两段长度减一。
先来考虑最后原地转圈会产生多少字符串,这些字符串显然不会和陷入循环之前某一字符串相同。一个字符串的所有循环移位?考虑复制一份字符串放在原串后,\(S'=S+S\),这样每一个循环移位,对应新串 \(S'\) 一个长度为 \(n\) 的子串 \(S'[l,r]\),其中 \(l\in[1,n]\),不妨用 \(l\) 来代指这个循环移位和这个子串。用 KMP 求出 \(S'\) 的最小周期 \(x\),则从 \(x+1\) 开始的循环移位都会与 \(1\sim x\) 某一个循环移位相同。所以这部分对答案的贡献即为 \(x\)。
不妨假设 \(S\) 中 \(1\) 的个数不少于 \(0\) 的个数。我们显然可以翻转 \(01\),再翻转字符串,来得到另一个答案相同,而颠倒 \(01\) 的字符串。
尝试模拟这个过程,得到最终陷入循环时串的形态,并顺便求出消除过程持续最久的时刻 \(mx\),以获得这一部分有多少不同的字符串。
考虑从右向左做,并维护一个栈,存放 \(01\) 段的初始左端点和长度。每次加入 \(1\) 段的时候,不断尝试消除 \(0\) 段即可。细节暂且不谈,这里还遗留一个小问题,就是某一个位于最左侧的 \(0\) 段可能不能被更左边的 \(1\) 段完全消除,而是依靠环的性质,靠串尾某些 \(1\) 段来消除。考虑如何优雅解决这种情况,由 Raney 引理,我们只需要让这个串从一个位置开始,使得任意前缀 \(1\) 的个数不少于 \(0\) 的个数即可。
现在来考虑细节。设当前 \(1\) 段的长度为 \(c\),初始左端点为 \(i+1\),尝试消除的 \(0\) 段长度为 \(k\),初始左端点为 \(l\)。
-
\(k\gt c\):
\(1\) 段被消耗完,留下 \(0\) 段,\(0\) 段剩余长度为 \(k-c+1\),并且需要给初始左端点 \(c-1\) 的偏移量,因为 \(0\) 段左侧 \(c-1\) 部分被消耗掉了,新的 \(0\) 段的初始左端点就是原先初始左端点加 \(c-1\)。考虑持续的时间 \(r\),即对 \(mx\) 的贡献。显然有一些式子:\(i+1+r=\mathrm{newR}-\mathrm{newC}+1\),\(\mathrm{newR}=\mathrm{curR}-r+1\),\(\mathrm{curR}=l+k-1\),化简后 \(r=\frac{1}{2}(l-1-i-c)\)。\(l-1-i-c\) 显然是 \(2\) 的倍数。 -
\(k\le c\):
\(0\) 段被消耗完,\(1\) 段剩余长度为 \(c-k+1\),初始左端点不受影响。类似有式子:\(\mathrm{curR}-r+1=(i+1)+r-1+(c-k+1)\)。得到 \(r=\frac{1}{2}(l+2k-1-i-c)\),显然结果是整数。
此外还有细节,若 \(S\) 最右侧有一个长度为 \(1\) 的 \(1\) 段,可能可以和串首 \(1\) 合并成一个 \(1\) 段。根据 Raney 的实现,一些实现下不需要处理串首单独 \(1\) 的情况。
有了最终每一个 \(1\) 段在原串上的左端点和长度,就能根据 \(mx\) 来偏移得到最终字符串。剩余部分用 \(01\) 来补齐即可。
有一个细节,若最终一个 \(1\) 段也没有,需要特判。
时间复杂度 \(\mathcal{O}(n)\)。
模数显然是诈骗。
代码
可视化工具
#include <cstdio>
#include <iostream>
#include <map>
using namespace std;
int main() {
string s;
cin >> s;
map<string, bool> mp;
int n = s.length();
while (!mp[s]) {
mp[s] = true;
for (char x : s)
cout << (x == '1' ? '@' : ' ');
cout << endl;
string t = s;
for (int i = 0; i < n; ++i)
if (s[i] == '0' && s[(i + 1) % n] == '1')
swap(t[i], t[(i + 1) % n]);
s = t;
}
return 0;
}
小样例
4
1100110010
11101111100001100000
1010101010101011111111111111110001111000000010101010101010
101011111111111101010001101001010101000000010101011111100101010101010
适当写了注释,区分每一步流程。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e7 + 10;
int n, fail[N << 1];
char s[N << 1];
struct {
int l, c;
} st[N];
void solve() {
scanf("%s", s + 1);
n = strlen(s + 1);
if (n == 1) {
puts("1");
return;
}
if (count(s + 1, s + n + 1, '0') * 2 > n) {
for (int i = 1; i <= n; ++i) s[i] ^= 1;
reverse(s + 1, s + n + 1);
// ensure count(1) >= count(0)
}
int p = 0, mi = 0;
for (int i = 1, f = 0; i <= n; ++i) {
int v = s[i] == '0' ? -1 : 1;
f += v;
if (f < mi) mi = f, p = i;
}
rotate(s + 1, s + p + 1, s + n + 1);
// Raney
int top = 0, mx = 1;
for (int i = n, c = 0; ~i; --i) {
if (s[i] == s[i + 1]) ++c;
else {
if (c > 1) {
if (s[i + 1] == '0') {
st[++top] = { i + 1, c };
} else {
while (top && s[st[top].l] == '0' && c > 1) {
int k = st[top].c, l = st[top].l;
if (k > c) {
int v = l - 1 - i - c;
if (v & 1) throw;
mx = max(mx, v >> 1);
st[top].c -= c - 1;
st[top].l += c - 1;
c = -1;
break;
} else {
int v = l + 2 * k - 1 - i - c;
if (v & 1) throw;
mx = max(mx, v >> 1);
c = c - k + 1;
--top;
}
}
if (c > 1) {
st[++top] = { i + 1, c };
}
}
}
c = 1;
}
}
// use stack to simulation
if (s[n - 1] == '0' && s[n] == '1' && s[1] == '1') {
// a corner case
st[++top] = { n, 1 };
}
if (s[2] == '0' && s[1] == '1' && s[n] == '1') {
// if Raney is good, this won't be reached
st[++top] = { 1, 1 };
}
if (top == 0) {
// corner case
printf("%d\n", mx + 1);
return;
}
for (int i = 1; i <= n; ++i) s[i] = '?';
for (int i = 1; i <= top; ++i)
for (int j = st[i].l; j < st[i].l + st[i].c; ++j)
s[(j + mx - 1 - 1) % n + 1] = '1';
int k = 0;
for (int i = 1; i <= n; ++i) {
if (s[i] == '1') {
char o = '1';
for (int j = i - 1; j > k; --j)
s[j] = o ^= 1;
k = i;
}
}
{
char o = '1';
for (int j = k + 1; j <= n; ++j)
s[j] = o ^= 1;
}
// get the final string
for (int i = 1; i <= n; ++i) s[n + i] = s[i];
n <<= 1, s[n + 1] = '\0';
for (int i = 1, j = fail[0] = -1; i <= n; ++i) {
while (~j && s[i] != s[j + 1]) j = fail[j];
fail[i] = ++j;
}
// KMP
int cur = n - fail[n];
printf("%d\n", cur + mx - 1);
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/19019908。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。

浙公网安备 33010602011771号