CF213B Numbers 题解

CF213B Numbers

洛谷题面

CF Problem

CF Contest

题目描述

给定一个正整数 N N N 和长度为 10 10 10 的数组 A 0 , A 1 , A 2 , … , A 9 A_0,A_1,A_2,\dots,A_9 A0,A1,A2,,A9,请求满足以下条件的数的个数:

  1. 长度不超过 N N N
  2. 数字 i i i 的出现次数至少为 A i A_i Ai
  3. 无前导 0 0 0

数据范围

1 ≤ n ≤ 100 1 \leq n \leq 100 1n100

0 ≤ a [ i ] ≤ 100 0 \leq a[i] \leq 100 0a[i]100

思路

题目要求计算构造一个长度不超过 n n n 、无前导零、规定数字个数的数的方法个数,对于这一题目,可以尝试使用 DP 求解。

f [ i ] [ j ] f[i][j] f[i][j] 表示使用 1 , 2 , … , j 1,2,\ldots,j 1,2,,j 可以构成多少个 i i i 位的满足条件的数

现在考虑如何进行转移,先枚举 i , j i,j i,j 表示当前的状态,再枚举 k k k ,表示向数中添加 k k k j j j

对于这 k k k j j j ,可以放在原数中任意两数位之间,也可以放在数尾,除 0 0 0 外,也都可以放在数的开头。

由此可以想到使用排列组合,对于这种有多个物品需要放入多个空中的问题,可以使用插板法

  • 对于 j ≠ 0 j \neq 0 j=0 的情况:

    举一个例子(以下例子中 i = 4 , k = 6 i=4,k=6 i=4,k=6

    ◯ 1 ◯ 2 ◯ 3 ◯ 4 ◯ 4 个数位有 5 个可以放置的空 \bigcirc 1 \bigcirc 2 \bigcirc 3 \bigcirc 4 \bigcirc\\ 4 个数位有 5 个可以放置的空 12344个数位有5个可以放置的空

    对于已经放好的 i i i 个数位,我们有 i + 1 i+1 i+1 个空可以放入 j j j

    1   ∣   2   ∣   3   ∣   4   ∣   5    6 6 个 j 插入 4 个隔板就可以分成 5 组 1~|~2~|~3~|~4~|~5~~6\\ 6 个 j 插入 4 个隔板就可以分成 5 组 1  2  3  4  5  66j插入4个隔板就可以分成5

    对于要放入的 k k k j j j ,我们需要插入 i + 1 − 1 = i i+1-1=i i+11=i 个隔板,将其分割为 i + 1 i+1 i+1 组。

    由于插板法分割的一组数至少有一个数字,但原数中的两个数位间却可以不放入 j j j

    为解决这个问题,可以先加入 i + 1 i+1 i+1 j j j ,使插板后的每一组先分到一个 j j j ,再从每一组中拿出一个 j j j ,这样分到一个 j j j 的组就会变为没有分到,而插板的方案个数并不改变。

    1    2    3   ∣   4    5    6    7   ∣   8    9   ∣   10   ∣   11 先将 6 个 j 补为 11 个,再插入 4 个隔板 11 个 j 中有 10 个可以插入隔板的空 1~~2~~3~|~4~~5~~6~~7~|~8~~9~|~10~|~11\\ 先将 6 个 j 补为 11 个,再插入 4 个隔板\\ 11 个 j 中有 10 个可以插入隔板的空 1  2  3  4  5  6  7  8  9  10  11先将6j补为11个,再插入4个隔板11j中有10个可以插入隔板的空

    所以我们最终我们要在 k + i + 1 k+i+1 k+i+1 j j j ,也就是 k + i k+i k+i 个空中插入 i i i 个隔板,可以用组合数来表示,方案数为 ( k + i i ) {k+i \choose i} (ik+i)


  • 对于 j = 0 j = 0 j=0 的情况:

    1 ◯ 2 ◯ 3 ◯ 4 ◯ 4 个数位有 4 个可以放入 j 的空 1 \bigcirc 2 \bigcirc 3 \bigcirc 4 \bigcirc\\ 4 个数位有 4 个可以放入 j 的空 12344个数位有4个可以放入j的空

    原理与 j ≠ 0 j \neq 0 j=0 的情况相同,只不过我们现在只有 i i i 个空可以放置 j j j

    也就是说,我们只需要插入 i − 1 i-1 i1 个隔板,将要放入的 k k k j j j 分割为 i i i 组。

    插板时需先加入 i i i j j j ,共 k + i − 1 k+i-1 k+i1 个空中插入 i − 1 i-1 i1 个隔板,故方案数为 ( k + i − 1 i − 1 ) {k+i-1 \choose i-1} (i1k+i1)

知道了添加一种数字可行的方案数就可以写出 DP 转移方程了。

  • 对于 j ≠ 0 j \neq 0 j=0 的情况:

    ∀ k ∈ [ a [ j ] , n − i ] f [ i + k ] [ j ] = f [ i ] [ j − 1 ] ⋅ ( k + i i ) \forall k \in [a[j],n-i]\\ f[i+k][j] = f[i][j-1] \cdot {k+i \choose i} k[a[j],ni]f[i+k][j]=f[i][j1](ik+i)

  • 对于 j = 0 j = 0 j=0 的情况:

    ∀ k ∈ [ a [ 0 ] , n − i ] f [ i + k ] [ 10 ] = f [ i ] [ 9 ] ⋅ ( k + i − 1 i − 1 ) \forall k \in [a[0],n-i]\\ f[i+k][10] = f[i][9] \cdot {k+i-1 \choose i-1} k[a[0],ni]f[i+k][10]=f[i][9](i1k+i1)

解释/说明

数字转移的顺序为 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0 1,2,3,4,5,6,7,8,9,0 1,2,3,4,5,6,7,8,9,0必须最后转移 0 0 0 ,因为 0 0 0 是有特殊处理的,只有在其余数字全部放完后才能确定放入的 0 0 0 不会成为前导零。

0 0 0 是最后一个转移,并且 1 1 1 转移的时候已经占用了下标 0 0 0 ,因此, 0 0 0 的下标是 10 10 10

组合数需要使用杨辉三角预处理出来。

记得开 long long ,DP数组与组合数相乘时会爆 int 。

详细了解插板法可以看这里

代码

#include<bits/stdc++.h>
#define ll long long//需要开long long
using namespace std;

const ll mod=1e9+7;
int n,a[20];
ll f[110][20],c[110][110];

int main()
{
    scanf("%d",&n);
    for(int i=0;i<=9;i++)
        scanf("%d",&a[i]);
    c[0][0]=1;
    for(int i=1;i<=n;i++)//杨辉三角预处理组合数
    {
        c[i][0]=c[i][i]=1;
        for(int j=1;j<i;j++)
            c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
    }
    f[0][0]=1;//什么都不放,1种方案
    for(int i=0;i<=n;i++)//现在放到第i位(从i+1位开始放)
    {
        for(int j=1;j<=9;j++)//放入j
            for(int k=a[j];k<=n-i;k++)//至少放a[j]个,长度不超过n,最多放n-i个
                f[i+k][j]=(f[i+k][j]+f[i][j-1]*c[k+i][i])%mod;//j!=0时的转移方程
        for(int k=a[0];k<=n-i;k++)
            f[i+k][10]=(f[i+k][10]+f[i][9]*c[k+i-1][i-1])%mod;//j=0时的转移方程
    }
    ll ans=0;
    for(int i=1;i<=n;i++)
        ans=(ans+f[i][10])%mod;
    printf("%lld\n",ans);
    return 0 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值