P2602 [ZJOI2010] 数字计数(数位dp入门题)

在这里插入图片描述
数位dp入门题.
首先考虑这样的一个问题:定义 d p i dp_i dpi为满 i i i位数字中,每个数字出现次数.
因为满 i i i位的关系,每个数字的出现次数一定是一样的.
考虑这个数字是 j j j,首先,第 i i i位随便放一个数字(不管放不放 j j j,这个 j j j都暂时不要算进方案里面),然后用前 i − 1 i-1 i1位的方案.
d p i = 10 ∗ d p i − 1 dp_i=10*dp_{i-1} dpi=10dpi1
然后,我们确定第 i i i位直接放 j j j这个数字,此时 j j j的贡献算1,余下的 i − 1 i-1 i1位不算进答案
d p i = 1 0 i − 1 dp_i=10^{i-1} dpi=10i1
合起来就是 d p i = d p i ∗ 10 + 1 0 i − 1 dp_i=dp_i*10+10^{i-1} dpi=dpi10+10i1
这样我们可以解决以下的一个问题:第 i i i位是A,剩下的 i − 1 i-1 i1位是没有确定的情况下
每个数字出现的次数
首先是考虑第 i i i位取 0 到 A − 1 0到A-1 0A1中的任意一个数字,那么因为没有取到 A A A,后边数字可以任意地取,此时贡献是 A ∗ d p [ i − 1 ] A*dp[i-1] Adp[i1],注意,此时第 i i i位没有纳入贡献.
考虑第 i i i位的取值,如果取 0 到 A − 1 0到A-1 0A1,与 d p 数组类似 dp数组类似 dp数组类似,这些数字的出现次数需要加上 1 0 i − 1 10^{i-1} 10i1
特别地,考虑第 i i i位取A,我们需要十分地谨慎.此时我们要求 i − 1 i-1 i1位上只能取 0 到 n u m [ i ] 0到num[i] 0num[i]
接下来就是求这个 i − 1 i-1 i1位,每个位上取 0 到 n u m [ i ] 0到num[i] 0num[i]的数字个数.让这个东西为 c n t cnt cnt
这个东西说简单简单,说恶心也恶心,主要思路就是检查每个位置上到底有多少个数字经过.打个比方如数字 792 792 792,个位上一定经过了 792 792 792个数字,十位上经过了 79 79 79个数字,百位上经过了 7 7 7个数字.
最终答案是 792 + 79 + 7 792+79+7 792+79+7.我们发现什么?不就每个位置从高位开始前缀相加吗
计算完这个前缀值,不要忘了A本身也要加1.
最后一个问题:扣去包含前导0的方案,001这种数字前边的0不应当算进0的数目,应当扣掉.我们讨论有 i i i个前导0,后边的数字任取.
这些前导0分位来讨论,分别有 ∑ i = 1 n − 1 1 0 i \sum_{i=1}^{n-1}10^i i=1n110i,到 i − 1 i-1 i1是因为每个位置的前导0
第一次学这个数位dp,还不是很熟练,需要多加练习

/*
I don't wanna be left behind,Distance was a friend of mine.
Catching breath in a web of lies,I've spent most of my life.
Catching my breath letting it go turning my cheek for the sake of this show
Now that you know this is my life I won't be told what's supposed to be right
Catch my breath no one can hold me back I ain't got time for that.
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6+5;
const int INF = 1e9+7;
typedef long long ll;
typedef pair<int,int> pii;
#define all(a) (a).begin(), (a).end()
#define pb(a) push_back(a)
ll cnta[15],cntb[15];
ll pw[15];//pw[i] = 10^i
ll s[maxn];ll dp[15];
void get(ll n,ll *ans){
	int len = 0;memset(s,0,sizeof(s));
	while(n) s[++len] = n%10,n/=10;
	for(int i=len;i>=1;i--){
		for(int j=0;j<=9;j++) ans[j]+=dp[i-1] * s[i];
		for(int j=0;j<s[i];j++) ans[j]+=pw[i-1];//0~i-1任意取
		ll cnt = 0;//计算若干前缀的和(1,i-1)
		for(int j=i-1;j>=1;j--) cnt = cnt*10+s[j];
		ans[0]-=pw[i-1];
		ans[s[i]] +=(cnt+1);
	}
}
int main(){
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	ll L,R;cin>>L>>R;
	pw[0] = 1;
	for(int i=1;i<=15;i++){
		dp[i] = dp[i-1]*10 + pw[i-1];
		pw[i] = pw[i-1]*10;
	}
	get(R,cnta);
	get(L-1,cntb);
	for(int i=0;i<=9;i++) cout<<(cnta[i]-cntb[i])<<" ";
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

minato_yukina

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

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

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

打赏作者

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

抵扣说明:

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

余额充值