题目
给出一个数位为 n n n的数字 ( n < = 1 e 6 ) (n<=1e6) (n<=1e6),现在可以将该数字最多分成 k ( 1 < = k < = n ) k(1<=k<=n) k(1<=k<=n)段。定义一种分法的贡献为所有段的数字之和,求所有分法的贡献和。
例如对于样例,答案为 1 + 1 + 10 + 100 = 112 1+1+10+100=112 1+1+10+100=112
3
100
1|0|0 :1+0+0=1
1|00 :1+0=1
10|0 :10+0=10
100 :100
比较容易想到的一种解法是对于每一位,计算它作为个位、十位… …的贡献和。
例如要对于数字 123456 123456 123456,最多可以分成 6 6 6段,要计算 3 3 3作为十位的贡献次数。 我们需要在 4 4 4后面分割一次,还剩下最多 k − 2 k-2 k−2次分割机会和 n − 3 n-3 n−3个可分割点,方案数显然就是 C n − 3 0 + C n − 3 1 + . . . . . . + C n − 3 k − 2 C_{n-3}^{0}+C_{n-3}^{1}+... ... +C_{n-3}^{k-2} Cn−30+Cn−31+......+Cn−3k−2。
直接计算方案数时间复杂度过高,需要先掌握组合数前缀和的知识。
递推组合数前缀和
我们先来证明以下公式
C n m = C n − 1 m − 1 + C n − 1 m C_n^m=C_{n-1}^{m-1}+C_{n-1}^{m} Cnm=Cn−1m−1+Cn−1m
证明:
C n m = ( n ) ! ( n − m ) ! × ( m ) ! C_{n}^{m}=\frac{(n)!}{(n-m)!\times (m)!} Cnm=(n−m)!×(m)!(n)!
C n − 1 m − 1 + C n − 1 m = ( n − 1 ) ! ( n − m ) ! × ( m − 1 ) ! + ( n − 1 ) ! ( m ) ! × ( n − m − 1 ) ! = m × ( n − 1 ) ! ( n − m ) ! × ( m ) ! + ( n − m ) × ( n − 1 ) ! ( n − m ) ! × ( m ) ! = ( n ) ! ( n − m ) ! × ( m ) ! = C n m C_{n-1}^{m-1}+C_{n-1}^{m}=\frac{(n-1)!}{(n-m)!\times (m-1)!}+\frac{(n-1)!}{(m)!\times (n-m-1)!}= \frac{m\times (n-1)!}{(n-m)!\times (m)!}+\frac{(n-m)\times (n-1)!}{(n-m)!\times (m)!}=\frac{(n)!}{(n-m)!\times (m)!}=C_{n}^{m} Cn−1m−1+Cn−1m=(n−m)!×(m−1)!(n−1)!+(m)!×(n−m−1)!(n−1)!=(n−m)!×(m)!m×(n−1)!+(n−m)!×(m)!(n−m)×(n−1)!=(n−m)!×(m)!(n)!=Cnm
上面的公式在杨辉三角递推组合数中有所应用。
记 F ( n , m ) = ∑ i = 0 m C n m F(n,m)=\sum_{i=0}^{m}C_n^m F(n,m)=∑i=0mCnm,可以发现,这在杨辉三角中体现为某一列的前缀和。
有以下两个递推式:
F ( n , m ) = F ( n , m − 1 ) + C ( n , m ) F(n,m)=F(n,m-1)+C(n,m) F(n,m)=F(n,m−1)+C(n,m)
F ( n , m ) = 2 × F ( n − 1 , m ) − C ( n − 1 , m ) F(n,m)=2\times F(n-1,m)-C(n-1,m) F(n,m)=2×F(n−1,m)−C(n−1,m)
第一个公式很显然,前缀和的性质,也就是杨辉三角某一行前 m − 1 m-1 m−1个数的和再加上第 m m m个数,显然就是前 m m m个数的和。
第二个公式也不难,回想一下杨辉三角递推组合数的方式,除了最后一个数,其他数都被加了两遍。
好了, 到这为止,我们已经掌握了递推组合数前缀和。
解题
考虑算每一位对答案产生的贡献,假设这一位为 d ( 0 < = d < = 9 ) d(0<=d<=9) d(0<=d<=9),那么他能产生的贡献就是 d × 1 0 j × n u m d \times 10^j \times num d×10j×num,其中, n u m num num为让 d d d产生 1 0 j 10^j 10j的位权的分法总方案数。
要使 d d d产生 1 0 j 10^j 10j的位权,记 d d d的下标为 i i i,那么我们应在第 i + j i+j i+j位后面进行一次分割,剩下的分割点和分割次数就会产生若干方案数。
将 i + j i+j i+j分三种情况讨论:
- i + j > n i+j>n i+j>n,不合法的方案,无贡献。
- i + j = n i+j=n i+j=n,合法的方案, i + j i+j i+j这个分割点是“本来就有的”,不花费分割次数,因此方案数为 ∑ i = 0 k − 1 C n − j − 1 i \sum_{i=0}^{k-1}C_{n-j-1}^{i} ∑i=0k−1Cn−j−1i( n − j − 1 n-j-1 n−j−1个分割点, k − 1 k-1 k−1次分割机会)
- i + j < n i+j<n i+j<n,合法的方案,需要在位 i + j i+j i+j后面进行一次分割,因此方案数为 ∑ i = 0 k − 2 C n − j − 2 i \sum_{i=0}^{k-2}C_{n-j-2}^{i} ∑i=0k−2Cn−j−2i
对于情况2,若几个不同的位,位权 1 0 j 10^j 10j都存在,那么这几个不同的位以情况2的方式产生 1 0 j 10^j 10j的位权的方案数是一样的。并且如果位置 i i i存在产生位权 1 0 j 10^j 10j的方案,那么 i i i前面的位置也一定存在,这样一来,我们从后往前考虑每一位,对情况2的方案数做后缀和,每一位只需计算新增的贡献,优化了时间复杂度。
对于情况3,我们直接计算即可。
我们需要用的是 F ( i , k − 1 ) F(i,k-1) F(i,k−1)和 F ( i , k − 2 ) F(i,k-2) F(i,k−2), i i i从 0 0 0到 n − 1 n-1 n−1,可以通过上面的组合数前缀和性质进行递推。
代码
#include <bits/stdc++.h>
using namespace std;
const long long mod = 998244353;
const int N = 1e6 + 6;
char s[N];
long long qpow(long long a, long long b)
{
long long res = 1;
while (b)
{
if (b & 1)
res = res * a % mod;
b /= 2;
a = a * a % mod;
}
return res;
}
long long fact[N];
void init()
{
fact[0] = 1;
for (int i = 1; i < N; i++)
fact[i] = fact[i - 1] * i % mod;
}
long long inv(long long x)
{
return qpow(x, mod - 2);
}
long long C(long long n, long long m)
{
if (n < m)
return 0;
return fact[n] * inv(fact[m]) % mod * inv(fact[n - m]) % mod;
}
long long pwd10[N];
long long A[N]; //预处理 F(n,k-2)
long long B[N]; //预处理 F(n,k-1)
long long n, k;
void solve()
{
pwd10[0] = 1;
for (int i = 1; i < n; i++)
pwd10[i] = pwd10[i - 1] * 10 % mod;
A[0] = k - 2 >= 0 ? 1 : 0;
B[0] = k - 1 >= 0 ? 1 : 0;
for (int i = 1; i < n; i++)
{
A[i] = (2 * A[i - 1] - C(i - 1, k - 2) + mod) % mod;
B[i] = (2 * B[i - 1] - C(i - 1, k - 1) + mod) % mod;
}
long long ans = 0, sum = 0;
for (int i = n; i >= 1; i--)
{
long long val = s[i] - '0';
if (i != n)
sum = (sum + A[i - 1] * pwd10[n - i - 1]) % mod;
ans = (ans + val * (B[i - 1] * pwd10[n - i] % mod + sum)) % mod;
}
cout << ans << endl;
}
int main()
{
init();
int t;
scanf("%d", &t);
while (t--)
{
scanf("%lld %s", &k, s + 1);
n = strlen(s + 1);
solve();
}
return 0;
}