杭电多校第八场05_Separated Number(组合数前缀和性质)

题目

Separated Number

给出一个数位为 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 k2次分割机会和 n − 3 n-3 n3个可分割点,方案数显然就是 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} Cn30+Cn31+......+Cn3k2

直接计算方案数时间复杂度过高,需要先掌握组合数前缀和的知识。

递推组合数前缀和

我们先来证明以下公式

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=Cn1m1+Cn1m

证明:

C n m = ( n ) ! ( n − m ) ! × ( m ) ! C_{n}^{m}=\frac{(n)!}{(n-m)!\times (m)!} Cnm=(nm)!×(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} Cn1m1+Cn1m=(nm)!×(m1)!(n1)!+(m)!×(nm1)!(n1)!=(nm)!×(m)!m×(n1)!+(nm)!×(m)!(nm)×(n1)!=(nm)!×(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,m1)+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(n1,m)C(n1,m)

第一个公式很显然,前缀和的性质,也就是杨辉三角某一行前 m − 1 m-1 m1个数的和再加上第 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分三种情况讨论:

  1. i + j > n i+j>n i+j>n,不合法的方案,无贡献。
  2. 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=0k1Cnj1i n − j − 1 n-j-1 nj1个分割点, k − 1 k-1 k1次分割机会)
  3. 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=0k2Cnj2i

对于情况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,k1) F ( i , k − 2 ) F(i,k-2) F(i,k2) i i i 0 0 0 n − 1 n-1 n1,可以通过上面的组合数前缀和性质进行递推。

代码

#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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hesorchen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值