2021杭电多校8 1005 Separated Number

题目描述

https://acm.hdu.edu.cn/showproblem.php?pid=7060

简要题解:给定一个长度为 $n$​ 的数字和整数 $k$​,求将这个数字划分成不超过 $k$​ 段的所有划分方法的价值和,定义一种划分方法的价值为它各段的价值和,一段数字的价值就是这个数字的值,例如:$114514$​ 的一种划分 $(114)(5)(14)$​ 的总价值是 $114+5+14=133$

$1\le k\le n\le 10^6$

Solution

我们考虑对于每个区间 $[l,r]$ 计算它的价值会被计算所少次

为了方便设 $l>1\wedge r <n$,$s=l-1,t=n-r$

容易得到次数就是 $\sum_{d=2}^{k-1}\sum_{i=1}^{d-1}\binom{s-1}{i-1}\binom{t-1}{d-i-1}=\sum_{d=2}^{k-1}\binom{s+t-2}{d-2}=\sum_{d=0}^{k-3}\binom{s+t-2}{d}$​,这是一个组合数前缀和的形式

容易发现,这个东西只跟 $[l,r]$ 的长度有关,我们令 $f_i$ 表示长度为 $i$ 的区间的和,那么可以得到随着 $i$ 的增加,这个组合数的上指标在减少,且总共只会移动 $O(n)$,我们可以 $O(n)$ 维护它的变化

另外前缀和后缀以及 $k\le 2$​ 的情况要单独考虑

时间复杂度 $O(n)$

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <iostream>
#include <vector>
#include <cstring>
#define maxn 1000010
#define ll long long
using namespace std;

const int p = 998244353;

ll pow_mod(ll x, ll n) {
ll s = 1;
for (; n; n >>= 1, x = x * x % p)
if (n & 1) s = s * x % p;
return s;
}

int n, k, s[maxn];
char c[maxn];

ll pre[maxn], suf[maxn], f[maxn];

ll inv[maxn], fac[maxn];
void init_C(int n) {
fac[0] = 1; for (int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % p;
inv[n] = pow_mod(fac[n], p - 2); for (int i = n - 1; ~i; --i) inv[i] = inv[i + 1] * (i + 1) % p;
}

ll C(int n, int m) { return n < m ? 0 : fac[n] * inv[m] % p * inv[n - m] % p; }

struct Csum {
int l, r; ll inv2, sum;
void init() { l = r = 1; sum = 2; inv2 = pow_mod(2, p - 2); }

ll move(int L, int R) { L = min(L, R);
while (r < R) sum = (2 * sum - C(r++, l)) % p;
while (l > L) sum = (sum - C(r, l--)) % p;
while (r > R) sum = (sum + C(--r, l)) * inv2 % p;
while (l < L) sum = (sum + C(r, ++l)) % p;
return sum;
}

ll get() { return sum; }
} S;

void work() {
cin >> k >> c + 1; n = strlen(c + 1);
s[n + 1] = 0; for (int i = n; i; --i) s[i] = s[i + 1] + c[i] - '0';
for (int i = 1; i <= n; ++i) pre[i] = (pre[i - 1] * 10 + c[i] - '0') % p;
ll mul = 1; for (int i = n; i; --i) suf[i] = (c[i] - '0') * mul % p, mul = mul * 10 % p;
suf[n + 1] = 0; for (int i = n; i; --i) suf[i] = (suf[i] + suf[i + 1]) % p;
if (k <= 2) {
if (k == 1) cout << pre[n] << "\n";
else {
ll ans = pre[n];
for (int i = 1; i < n; ++i)
ans = (ans + pre[i] + suf[i + 1]) % p;
cout << ans << "\n";
}
return ;
}
for (int i = 1; i <= n; ++i) f[i] = ((f[i - 1] - suf[n - i + 2]) * 10 + s[i]) % p;
for (int i = 1; i <= n; ++i) f[i] = (f[i] - pre[i] - suf[n - i + 1]) % p;
S.init(); ll ans = pre[n];
for (int i = 1; i <= n - 2; ++i)
ans = (ans + f[i] * S.move(k - 3, n - i - 2)) % p;
for (int i = 1; i < n; ++i)
ans = (ans + pre[i] * S.move(k - 2, n - i - 1)) % p;
for (int i = 2; i <= n; ++i)
ans = (ans + suf[i] * S.move(k - 2, i - 2)) % p;
cout << (ans + p) % p << "\n";
}

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);

int T; cin >> T; init_C(1000000);
while (T--) work();
return 0;
}