华科大-04. 美丽字符串

华科大-04. 美丽字符串

2023.10.24活动

「10·24」程序员节编程竞赛

设s下标从0开始。

我们分别求每个前缀的贡献。

对于s[i] = '0'的贡献,我们假设已经统计了前缀s[0...i]中的'0'的个数c0。

这时候,扩展串长度为2i+1,在排序后要保证串中心为'0',那么至少需要i+1个'0'字符。我们已经有了c0个'0'字符。还需要至少i+1-c0个字符。而在扩展串中我们可以操控的位置是i个,所以考虑i个位置中分别选i+1-c0个的情况,i+1-c0+1个的情况,等等,直到i个的情况。所以只需要计算组合数$C_{i}^{i+1-c0}+C_{i}^{i+1-c0+1}+\cdots+C_{i}^{i}$

转化一下得到$C_{i}^{c0-1}+C_{i}^{c0-2}+\cdots+C_{i}^{0}$。对于每一个前缀串,都需要计算这么一个组合数,直接遍历计算妥妥超时。必须$O(1)$计算出该值。

发现一个重大规律就是i和c0只增不减。
设$S_{i,j} = C_{i}^{0}+C_{i}^{1}+\cdots+C_{i}^{j}$

对于s[0...i]有j个'0',已知$S_{i,j}$的情况下,s[i+1]='1'时,我们只需要求$S_{i+1,j+1}$,并将其加入答案中;s[i+1]='0'时,我们需要求$S_{i+1,j}$。

通过杨辉三角发现一个组合恒等式$C_{i}^{j}+C_{i}^{j+1} = C_{i+1}^{j+1}$

$2*S_{i,j} - C_{i}^{j} = S_{i+1, j}$

$2*S_{i,j} - C_{i}^{j} + C_{i+1, j+1} = S_{i+1, j+1}$

至此状态转移只需$O(1)$做到。总时间复杂度$O(n)$

对于s[i]='1'的情况,同理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class Solution {
public:
using ll = long long;
const ll MOD = 998244353;
#define N 200005
ll fac[N], inv_fac[N];
ll fpow(ll x, ll p, ll m) {
ll rt = 1;
while (p) {
if (p&1) rt *= x, rt %= m;
x *= x; x %= m;
p >>= 1;
}
return rt;
}
void PP() {
fac[0] = 1;
for (int i=1; i<N; i++) {
fac[i] = fac[i-1]*i%MOD;
}
inv_fac[N-1] = fpow(fac[N-1], MOD-2, MOD);
for (int i=N-2; i>=0; i--) {
inv_fac[i] = inv_fac[i+1]*(i+1)%MOD;
}
}
ll C(int n, int m) {
if (n<0 || m < 0 || n<m) return 0;
return fac[n]*inv_fac[m]%MOD*inv_fac[n-m]%MOD;
}
int beautifulString(string s) {
PP();
ll ans = 0, c1 = 0, p1 = 0, c0 = 0, p0 = 0;
int n = s.size();
for (int i=0; i<n; i++) {
if (s[i] == '1') c1++;
else c0++;

if (s[i] == '1') {
// 需要 i+1 个1,已经有c1个1,还需要至少i+1-c1个1,在i个位置分配,共计C_i^{i+1-c1} + ... + C_{i}^{i}方式
// 即C_{i}^0 + ... + C_{i}^{c1-1}
p1 = 2*p1-C(i-1, c1-2)+C(i, c1-1);
p1 = (p1+MOD)%MOD;
p0 = 2*p0-C(i-1, c0-1);
p0 = (p0+MOD)%MOD;
ans += p1;

} else {
// 需要 i+1 个0,已经有c0个0,还需要至少i+1-c0个0,在i个位置分配,共计C_i^{i+1-c0} + ... + C_{i}^{i}方式
// 即C_{i}^0 + ... + C_{i}^{c0-1}
// i-1时为c0-1个0,所以计算了C_{i-1}^0 + ... + C_{i-1}^{c0-2}
p0 = 2*p0-C(i-1, c0-2)+C(i, c0-1);
p0 = (p0+MOD)%MOD;
// i-1时为c1个1,所以计算了C_{i-1}^0 + ... + C_{i-1}^{c1-1},当前仍然为c1个1
p1 = 2*p1-C(i-1, c1-1);
p1 = (p1+MOD)%MOD;
ans += p0;
}

// cout << i << " " << p0 <<" " << p1 << endl;

ans %= MOD;
}
return ans;

}
};