[ZJOI2010]数字计数

题目传送门

题意

给你 l l l r r r ,问你 0 ∼ 9 0\sim9 09在这里面分别出现了多少次。

解题方法

这个题是数位 d p dp dp 的板子题,对于每一个数我们关注的是他第 i i i 位上的数是什么,而不是关注其他的东西,那么我们这个样子就非常好处理了,我们就一位一位向前转移好了,我们定义一个三维数组 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k],表示到第 i i i 位上首位数是 j j j k k k 出现的次数,初始化很好想,就是把左右的 d p [ 1 ] [ i ] [ i ] dp[1][i][i] dp[1][i][i] 都赋值为1,然后我们该如何转移呢,很好想的是:
d p [ i ] [ j ] [ k ] = ∑ t = 0 9 d p [ i − 1 ] [ t ] [ k ] dp[i][j][k]=\sum_{t=0}^{9}dp[i-1][t][k] dp[i][j][k]=t=09dp[i1][t][k]
但是这个样子我们显然少计算了在首位上对答案的更新,我们发现在 d p [ i ] [ j ] [ j ] dp[i][j][j] dp[i][j][j] 上贡献了 1 0 i − 1 10^{i-1} 10i1 j j j 的,因为在后面的位数都计算完毕以后,我们只需要计算在最顶位上 j j j 出现的次数即可,那么首位数上是 j j j 的数出现了 1 0 i − 1 10^{i-1} 10i1 次,那么答案就增加了这么多,最后我们统计答案的时候,我们先计算出位数比端点的数的位数少的 d p dp dp 值统计出来,然后再统计位数一样的答案,对于每一位,我们只需要把小于这位的答案加起来,然后要注意一点的是,我们还要统计在这位数上面的那些数位对答案的贡献。
最后我们运用一下前缀和的思想,算出 0 ∼ r 0\sim r 0r的答案减去 0 ∼ l − 1 0\sim l-1 0l1的答案就是 l ∼ r l\sim r lr的答案,但是这个数位 d p dp dp算出来的值并不是统计 i i i之前的数,而是 i − 1 i-1 i1,所以要记得加一统计答案。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long a[15];
long long dp[15][10][10];
long long ans[10][2];
long long ksm(long long x,long long y)
{
	long long ans=1;
	while(y)
	{
		if(y&1)ans*=x;
		x*=x;
		y>>=1;
	}
	return ans;
}
long long weishu;
void work(long long x,long long zkt)
{
	memset(dp,0,sizeof(dp));
	memset(a,0,sizeof(a));
	weishu=0;
	while(x)
	{
		weishu++;
		a[weishu]=x%10;
		x/=10;
	}
	for(long long i=0;i<=9;i++)  
		dp[1][i][i]=1;
	for(long long i=2;i<=weishu;i++)
	  for(long long j=0;j<=9;j++)
	    {
	    	for(long long k=0;k<=9;k++)
	    	{
	    		for(long long t=0;t<=9;t++)
	    		  dp[i][j][t]+=dp[i-1][k][t];
			}
			dp[i][j][j]+=ksm(10,i-1);
		}
	for(long long i=1;i<weishu;i++)
	  for(long long j=1;j<=9;j++)//注意前导零
	    for(long long k=0;k<=9;k++)
	      ans[k][zkt]+=dp[i][j][k];
	for(long long i=1;i<a[weishu];i++)
	  for(long long k=0;k<=9;k++)
	    ans[k][zkt]+=dp[weishu][i][k];
	for(long long i=weishu-1;i;i--)
	{
		for(long long j=0;j<a[i];j++)
		{
			for(long long k=0;k<=9;k++)
			  ans[k][zkt]+=dp[i][j][k];
		}
		for(long long p=weishu;p>i;p--)
		  ans[a[p]][zkt]+=a[i]*ksm(10,i-1);
	}
}
int main()
{
	long long x,y;
	scanf("%lld%lld",&x,&y);
	work(y+1,0);work(x,1);
	for(long long i=0;i<=9;i++)
	  cout<<ans[i][0]-ans[i][1]<<' ';
	return 0;
 } 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值