数位dp

数位dp的写法貌似很多,记忆化搜索,dp预处理然后试填什么的,我写的一般都是后者

数位dp写着写着就偏了,感觉本质上就是个数学题。。。

The Counting Problem

求 [L,R] 内每个数码(0~9)出现的次数。


首先可以把区间的解拆成两个前缀和相减的模式,于是问题就变成了求[1,R]内每个……

众所周知这是一道数位dp,但是对于只听过概念的我来说怎么写呢

当然是愉快的分类讨论啦。

(从某种意义上来讲这是段废话不是什么题解)位数小于R的是一种情况,这可以快速计算。然后就是位数和R一样的数的贡献,这里可以在分一下,分成最高位与R相同和不相同两种情况。然后肯定要dp,状态设什么,这样,那样,过样例了!怎么wa没了,哪里错了,这个dp有问题啊,还是不对啊,??要dp干嘛用??直接算不就好了。。。

于是我调了好久好久,最终发现不用dp。。。这就变成了一道大力分类讨论数学题。。。代码又臭又长

以下才是正传。后来我觉得这样不行啊,决心痛改前非(此时已经做了几道数位dp题)。

套路是这样的,先预处理一个dp,f[i][j]表示数字位数为i的数中j出现的次数,并且允许出现前导0。然后用试填法求解

对于R,先考虑R的最高位,考虑在这一位填的数是什么,填的数必定不能大于R的最高位。如果小于最高位的话,之后的位上无论填什么都是满足条件的(因为都不会大于R),(假设k是R的位数)于是这对应的答案就是f[k-1][0~9]
否则最高位上填的数就与R的最高位相等,继续往下枚举算剩下的贡献。可以说我们枚举i(1-k),统计1~(i-1)位于R相同第i位与R不同的数带来的贡献(位数从高位往低位数)。

注意,这样的统计方式并不会统计R的贡献,所以需要特殊处理R。

写完以后发现我写挂了,不经怀疑人生(两次死在1 121上的痛楚),愉快的发现前导0挂死了我。

上面的做法不能很好的处理掉前导0 。因为比R的小的数,可能位数比它小,这就对应着在R的前若干位上填0,但是这些0是前导0,不能计入答案,而上述算法将它们计入了答案。

对于这个bug,可以人脑统计一下多算的0,然后减掉。或者说你再分情况讨论一下,讨论位数和R一样和比它小两种情况。枚举位数,强制要求其最高位不为0,计数可以在dp中在开一个数组直接预处理,位数和R一样的,就在试填的时候强制要求最高位不能填0(这样写感觉比较清晰)

人脑计算系列

#include <bits/stdc++.h>
using namespace std;
long long Pow[15],f[15][15],ans[2][15],L,R;
long long a[15];
void Prework()//dp预处理
{
	Pow[0]=1;
	for (int i=1;i<=12;i++)
	  Pow[i]=Pow[i-1]*10;
	memset(f,0,sizeof(f));
	for (int i=1;i<=12;i++)
	  for (int j=0;j<=9;j++)
	  {
	    f[i][j]=f[i-1][j]*10;
		f[i][j]+=Pow[i-1];
      }
}
void calc(long long N,int num)
{
	int k=0;
	while (N>0) {a[++k]=N%10;ans[num][a[k]]++;N/=10;}
	for (int i=k;i>=1;i--)
	{
		for (int j=0;j<=9;j++)
		  ans[num][j]+=a[i]*f[i-1][j];
		for (int j=0;j<a[i];j++)
		  if (j>0||i<k) ans[num][j]+=Pow[i-1];
		for (int j=k;j>i;j--)
		  ans[num][a[j]]+=a[i]*Pow[i-1];
		  
	}
	for (int i=max(k-2,0);i>=0;i--)
	   ans[num][0]-=Pow[i];//减去多算的0,我也忘了为什么
}
int main()
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	Prework();
	scanf("%lld%lld",&L,&R);
	while (L||R)
	{
		memset(ans,0,sizeof(ans));
		calc(R,0);
		calc(L-1,1);
		for (int i=0;i<=9;i++)
		  printf("%lld ",ans[0][i]-ans[1][i]);
		printf("\n");
		scanf("%lld%lld",&L,&R);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值