A Ribbon for Tomorrow
A Ribbon for Tomorrow
(快速幂,组合数,逆元)
1. 题目简述
给定一个长度为 \(n\) 的二进制字符串 \(s\)(由 0 和 1 组成)。允许进行任意次如下操作:
- 选择两个下标 \(l \le r\),若当前字符串中 \(s_l = s_r\),则反转子串 \(s_l s_{l+1} \dots s_r\)。
问:从初始字符串出发,可以得到的不同的二进制字符串数量是多少?答案对 \(998244353\) 取模。
2. 核心观察:操作的不变量
通过分析操作的性质,我们可以找到三个在操作前后保持不变的量:
2.1 首字符不变
- 若操作包含第一位(即 \(l=1\)),反转后新的首字符是原来的 \(s_r\)。由于必须满足 \(s_l = s_r\),且 \(s_l = s_1\),所以新首字符仍等于原首字符。
- 若操作不包含第一位,首字符完全不受影响。
- 结论:字符串的 第 1 位字符 永远固定不变。
2.2 字符总数不变
- 反转操作只改变字符顺序,不改变字符本身。
- 结论:字符
0的总数 \(cnt_0\) 和字符1的总数 \(cnt_1\) 保持不变。
2.3 连续相同字符段(Run)的数量不变
定义:Run(连续段)是指字符串中极长的连续相同字符子串。例如
000是一个 0-run,11是一个 1-run。
- 反转一个两端字符相同的子串时,子串内部的顺序倒置,但子串两端字符相同,因此与左右邻接部分的连接处不会产生新的 Run,也不会导致现有 Run 消失。
- 结论:
0-run的数量 \(seg_0\) 和1-run的数量 \(seg_1\) 保持不变。
推论:由于 Run 的颜色一定是交替出现的,加上首字符固定,Run 的数量就决定了整个字符串的 Run 顺序框架。例如,若首字符为
0,且有 2 个 0-run 和 1 个 1-run,则 Run 顺序只能是:0-run, 1-run, 0-run。
3. 充分性证明:这些不变量是否足够?
题解的核心在于证明:任何满足上述不变量的状态都是可达的。
3.1 关键操作:交换同色 Run 的长度
考虑字符串中存在以下结构(两个 0-run 夹着一个 1-run):
\[0^a \; 1^b \; 0^c
\]
其中 \(a, b, c > 0\),上标表示该字符重复的次数。
我们选择左端点位于第一个 0-run,右端点位于第二个 0-run,反转整个子串。由于两端都是 0,反转后变为:
\[0^c \; 1^b \; 0^a
\]
中间的 \(1^b\) 反向后不变。
这意味着:我们可以在不改变中间 1-run 长度的情况下,任意交换两个 0-run 的长度。
3.2 推广到所有 Run
- 因为任意两个同色的 Run 之间一定隔着若干个异色 Run,我们可以利用上述操作,将长度在同色 Run 之间任意调换。
- 这相当于:你有 \(seg_0\) 个盒子,里面装着总数为 \(cnt_0\) 个 0,允许你任意交换两个盒子里的数量。显然,只要每个盒子最终非空,你可以达到任意一种分配方案。
- 同理,1 的分配也是独立的,且不会干扰 0 的分配。
结论:只要保证每个 Run 长度为正整数,且总数固定,所有长度组合都是可达的。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
const int MAXN = 1000000 + 5;
vector<ll> fact(MAXN), invfact(MAXN);
ll modpow(ll a, ll e) {
ll r = 1;
while(e) {
if(e & 1) r = r * a % MOD;
a = a * a % MOD;
e >>= 1;
}
return r;
}
void init() {
fact[0] = 1;
for(int i = 1; i < MAXN; i ++) fact[i] = fact[i - 1] * i % MOD;
invfact[MAXN-1] = modpow(fact[MAXN - 1], MOD - 2);
for(int i = MAXN - 2; i >= 0; i --) invfact[i] = invfact[i + 1] * (i + 1) % MOD;
}
ll C(int n, int k) {
if(k < 0 || k > n) return 0;
return fact[n] * invfact[k] % MOD * invfact[n-k] % MOD;
}
void solve() {
int n;
string s; cin >> n >> s;
int cnt0 = 0, cnt1 = 0;
int seg0 = 0, seg1 = 0;
for(char ch : s) {
if(ch == '0') cnt0 ++;
else cnt1 ++;
}
seg0 = seg1 = 0;
for(int i = 0; i < n; ) {
int j = i;
while (j < n && s[j] == s[i]) j ++;
if (s[i] == '0') seg0 ++;
else seg1 ++;
i = j;
}
ll ways0 = 1, ways1 = 1;
if(cnt0 > 0) ways0 = C(cnt0 - 1, seg0 - 1);
if(cnt1 > 0) ways1 = C(cnt1 - 1, seg1 - 1);
ll ans = ways0 * ways1 % MOD;
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
init();
int t; cin >> t;
while(t --) solve();
return 0;
}

浙公网安备 33010602011771号