【蓝桥杯 2023 国 Python A】2023

【蓝桥杯 2023 国 Python A】2023


蓝桥杯专栏:2023 国 Python A

算法竞赛:数学,组合数学,容斥原理,二项式定理

题目链接:洛谷【蓝桥杯 2023 国 Python A】2023

题目描述

给定 n , m n, m n,m,请求出所有 n n n 位十进制整数中有多少个数中恰好出现了 m m m 个 2023 2023 2023。

例如 00202312023 00202312023 00202312023 是一个 11 11 11 位的出现了 2 2 2 个 2023 2023 2023 的十进制整数。

由于结果可能很大,请输出答案对 998 , 244 , 353 998,244,353 998,244,353 取模的结果。
输入格式

输入一行包含两个整数 n , m n,m n,m,用一个空格分隔。

输出格式

输出一行包含一个整数表示答案。
数据范围

对于 40 % 40\% 40% 的评测用例, n ≤ 10 5 , m ≤ 10 n \le 10^5,m \le 10 n≤105,m≤10;

对于所有评测用例, 4 ≤ n ≤ 10 5 , 0 ≤ 4 m ≤ n 4 \le n \le 10^5,0 \le 4m \le n 4≤n≤105,0≤4m≤n。


题目分析


题目大意

给出两个正整数 n , m n,m n,m,求在 n n n 位十进制数中恰好有 m m m 个 2023 2023 2023 的数字个数,允许含有前导零。

题目思路

使用二项式反演 将问题转化为计算至少出现 k k k 个 2023 2023 2023 的方案数,然后通过容斥原理 得到恰好出现 m m m 个的方案数。

组合计数

  1. 在 n n n 位中选出 k k k 个 2023 2023 2023 可以通过确定首位置的方式来确定 2023 2023 2023 的位置,则转化成在 n − 3 k n-3k n−3k 个数里选出 k k k 个数;
  2. 选出 k k k 个 2023 2023 2023,则剩余 n − 4 k n-4k n−4k 位,由于允许前导零存在,故每一位都可以填 0 − 9 0-9 0−9 的任何数字,故剩余项方案数为 10 n − 4 k 10^{n-4k} 10n−4k。

综上所述,在 n n n 位十进制数中至少出现 k k k 个 2023 2023 2023 的方案数为 g ( k ) = ( n − 3 k k ) × 10 n − 4 k g(k)={n-3k\choose k}\times 10^{n-4k} g(k)=(kn−3k)×10n−4k。

也可以使用隔板法来得出:

  1. 有 k k k 个 2023 2023 2023 作为隔板,则有 k + 1 k+1 k+1 个可摆放的位置;
  2. 有 n − 4 k n-4k n−4k 个"位"的自由位置。

使用隔板法:将 n − 4 k n-4k n−4k 个自由位置分配到 k + 1 k+1 k+1 个位置上,相当于解方程:
x 1 + x 2 + x 3 + . . . . . . + x k + 1 = n − 4 k , x i ≥ 0 x_1+x_2+x_3+......+x_{k+1}=n-4k,x_i \ge 0 x1+x2+x3+......+xk+1=n−4k,xi≥0

令 y i = x i + 1 y_i=x_i+1 yi=xi+1,则
y 1 + y 2 + y 3 + . . . . . . + y k + 1 = n − 4 k + k + 1 , y i ≥ 1 y_1+y_2+y_3+......+y_{k+1}=n-4k+k+1,y_i \ge 1 y1+y2+y3+......+yk+1=n−4k+k+1,yi≥1

相当于在 n − 4 k + k + 1 = n − 3 k + 1 n-4k+k+1=n-3k+1 n−4k+k+1=n−3k+1 之间放 k k k 个隔板(形成 k + 1 k+1 k+1 个间隙),因为 y i ≥ 1 y_i \ge 1 yi≥1,所以有 n − 3 k n-3k n−3k 个可放隔板的间隙,所以方案数为 ( n − 3 k k ) {n-3k\choose k} (kn−3k)。

容斥原理

令 A i A_i Ai 为至少存在 i i i 个, B i B_i Bi 为恰好存在 i i i 个,最多存在 n n n 个,容斥原理则为 A 1 = B 1 − B 2 + B 3 − B 4 + . . . . . . + ( − 1 ) n − 1 × B n A_1=B_1-B_2+B_3-B_4+......+(-1)^{n-1} \times B_n A1=B1−B2+B3−B4+......+(−1)n−1×Bn 也就是 A 1 = ∑ i = 1 n ( − 1 ) n − 1 × B i A_1=\sum_{i=1}^{n} (-1)^{n-1} \times B_i A1=∑i=1n(−1)n−1×Bi。

二项式反演

  1. f ( k ) f(k) f(k):恰好出现 k k k 个 2023 2023 2023 的 n n n 位数字串的个数。
  2. g ( k ) g(k) g(k):至少出现 k k k 个 2023 2023 2023 的 n n n 位数字串的个数(指定 k k k 个位置)。

则存在基本关系
g ( k ) = ∑ i = k ⌊ n / 4 ⌋ ( i k ) × f ( i ) g(k)=\sum_{i=k}^{\lfloor n/4\rfloor} {i \choose k} \times f(i) g(k)=i=k∑⌊n/4⌋(ki)×f(i)

解释:若一个数字串实际有 i i i 个 2023 2023 2023( i ≥ k i \geq k i≥k),选择其中任意 k k k 个位置作为指定位置,则共有 ( i k ) \binom{i}{k} (ki) 种选择方式,因此 f ( i ) f(i) f(i) 会被计入 g ( k ) g(k) g(k) 中 ( i k ) \binom{i}{k} (ki) 次。

二项式反演定理
f ( n ) = ∑ i = 0 n ( n i ) g ( i ) ⇔ g ( n ) = ∑ i = 0 n ( − 1 ) n − i ( n i ) f ( i ) f(n)=\sum_{i=0}^{n} {n \choose i} g(i)\Leftrightarrow g(n)=\sum_{i=0}^{n} (-1)^{n-i} {n \choose i} f(i) f(n)=i=0∑n(in)g(i)⇔g(n)=i=0∑n(−1)n−i(in)f(i)

变体
f ( n ) = ∑ i = n m ( i n ) g ( i ) ⇔ g ( n ) = ∑ i = n m ( − 1 ) i − n ( i n ) f ( i ) f(n)=\sum_{i=n}^{m} {i \choose n} g(i)\Leftrightarrow g(n)=\sum_{i=n}^{m} (-1)^{i-n} {i \choose n} f(i) f(n)=i=n∑m(ni)g(i)⇔g(n)=i=n∑m(−1)i−n(ni)f(i)

故可得出
f ( k ) = ∑ i = k ⌊ n / 4 ⌋ ( − 1 ) i − k ( i k ) g ( i ) f(k)=\sum_{i=k}^{\lfloor n/4\rfloor} (-1)^{i-k} {i\choose k} g(i) f(k)=i=k∑⌊n/4⌋(−1)i−k(ki)g(i)

其中, g ( i ) g(i) g(i) 的计算在上述组合计数中。

最终公式
f ( m ) = ∑ k = m K ( − 1 ) k − m × ( k m ) × g ( k ) f(m)=\sum_{k=m}^{K} (-1)^{k-m} \times {k\choose m} \times g(k) f(m)=k=m∑K(−1)k−m×(mk)×g(k)

其中 K = ⌊ n / 4 ⌋ K=\lfloor n/4\rfloor K=⌊n/4⌋ 即 n n n 位十进制数中最大的 2023 2023 2023 可能出现的个数, g ( k ) = ( n − 3 k k ) × 10 n − 4 k g(k)={n-3k\choose k}\times 10^{n-4k} g(k)=(kn−3k)×10n−4k 即 n n n 位十进制数中至少出现 k k k 个 2023 2023 2023 的数字个数。


知识总结


数学知识

  1. 二项式定理
  2. 二项式反演
  3. 容斥原理
  4. 组合数学概念、表示及公式
  5. 乘法原理
  6. 逆元概念
  7. 费马小定理
  8. 模运算公式

算法知识

  1. 快速幂求逆元
  2. 求组合数(阶乘与逆元)

算法实现


预处理

  1. f a c t i fact_i facti 存储阶乘
  2. i n f a c t i infact_i infacti 存储阶乘的逆元
  3. p o w 10 i pow10_i pow10i 存储 10 10 10 的幂
  4. 使用快速幂求逆元

主函数循环

  1. 遍历可能的 2023 2023 2023 的个数 k k k(从 m m m 到 ⌊ n / 4 ⌋ \lfloor n/4 \rfloor ⌊n/4⌋);
  2. 计算 g ( k ) = ( n − 3 k k ) × 10 n − 4 k g(k)={n-3k\choose k}\times 10^{n-4k} g(k)=(kn−3k)×10n−4k;
  3. 计算二项式系数 ( k m ) {k \choose m} (mk);
  4. 根据容斥原理调整正反 ( − 1 ) k − m (-1)^{k-m} (−1)k−m;
  5. 累计到 a n s ans ans 最终答案,注意 :由于容斥原理会加会减,且模运算会改变数的实际大小,为防止出现负数,在模运算之前加上模数,ans=(ans+(long long)c_1*c_2*sign+mod)%mod

时间复杂度 O ( n ) \mathcal{O}(n) O(n),空间复杂度 O ( n ) \mathcal{O}(n) O(n)。

AC Code

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10,mod = 998244353;
typedef long long ll;
int fact[N],infact[N],pow10[N];//fact[i]--i 的阶乘,infact[i]--i 的阶乘逆元,pow10[i]--10 的 i 次幂
int qmi(int a,int k)//快速幂求逆元
{
	int res=1;
	while (k)
	{
		if (k&1) res=(ll)res*a%mod;
		a=(ll)a*a%mod;
		k>>=1;
	}
	return res;
}
void init(int n)
{
	fact[0]=infact[0]=pow10[0]=1;//初始化
	for (int i=1;i<=n;i++)
	{
		fact[i]=(ll)fact[i-1]*i%mod;//阶乘计算
		pow10[i]=(ll)pow10[i-1]*10%mod;//10 的幂计算
	}
	infact[n]=qmi(fact[n],mod-2);
	for (int i=n-1;i>=1;i--)
		infact[i]=(ll)infact[i+1]*(i+1)%mod;//阶乘逆元计算
}
int main()
{
	int n,m,ans=0,sign=-1;
	scanf("%d%d",&n,&m);
	init(n);
	for (int i=m;i<=n/4;i++)
	{
		sign*=-1;
		int a=n-i*3;
		int c1=(ll)fact[a]*infact[i]%mod*infact[a-i]%mod*pow10[n-4*i]%mod;
		int c2=(ll)fact[i]*infact[m]%mod*infact[i-m]%mod;
		ans=(ans+(ll)c1*c2*sign+mod)%mod;//sign 有时为 -1,由于模运算,要加 mod,在模 mod,以防为负数
	}
	printf("%d",ans);
	return 0;
}

End

感谢观看,如有问题欢迎指出。

更新日志

  1. 2025/8/21 开始书写本篇 CSDN 博客,并完稿发布。

本篇博客最早由本人发布于洛谷文章广场,本篇博客对其进行了修改调整与完善丰富。