P4999 烦人的数学作业

知识点:数位DP

原题面

题目要求 :

给定两个正整数 \(a\)\(b\) , 求在 \([a,b]\) 中的所有整数中 ,
每个数的数字和

分析题意 :

发现 可以直接进行递推

  • \(\text{f[i][j][k]}\) 表示一个长度为 \(i\) 的数,其中最高位是 \(j\)\(k\) 这个数码一共出现的次数
    则 可以枚举最高位 , 来实现 \(\text{f[i][j][k]}\)\(\text{f[i-1][l][k]}\) 之间的转移
    状态转移方程:
    \(\text{f[i][j][k] = sum(f[i-1][l][k])} (0 \le l \le 9)\)

  • 发现 最高位虽然在多个数字中出现 , 但是 并没有做出过贡献
    对于一个长度为 \(i\) 的数,
    根据乘法原理, 可以计算出 最高位的贡献 :
    \(\text{f[i][j][j]}+= 10 ^ {(i-1)}\)

  • 由上 , 则可递推出所有长度的数的出现次数
    考虑 将其进行统计 :
    由于存在 选数的上界 , 则需进行分类讨论:

    1. 位数 \(<lth\) 的数, 必然满足条件 , 不会越界 , 直接计入答案
      直接枚举位数 , 最高位 , 和各位数字 , 并计入答案即可

    2. 位数为 \(lth\) 的数 , 保证最高位 \(<\) 最高位 可选最大值
      则 可枚举最高位 , 枚举各位数字 ,
      在保证最高位 \(<\) 最高位 可选的最大值 情况下,
      将贡献计入答案

    3. 位数为 \(lth\) 的数 , 第 \([i + 1,lth]\)高位 与 可选最大值 相同
      则可枚举 第 \(i\) 高位 , 再枚举各位数字
      在保证 第 \(i\) 高位 < \(i\) 高位 可选最大值 情况下,
      将贡献计入答案

      同时, 第 \([i + 1,lth]\) 高位 固定, 但是 其贡献并未计入答案
      同上 , 根据乘法原理 , 可以计算出 第 \(k\) 位的贡献 :
      \(\text{ans[num[k]] += num[i]} * 10 ^ {(k-1)}\);
      由于 第 \(i\) 位有 \(\text{num[i]}\) 种可能, 则需乘 \(\text{num[i]}\)

  • 通过前缀和 , 可以得到 \([a,b]\) 中每个数码 各出现了多少次.
    使用快速乘取余 , 求得每个数的贡献求和即可

#include <cstdio>
#include <cstring>
#include <ctype.h>
#define int long long
const int mod = 1e9 + 7;
//=============================================================
int T,a, b, lth, num[20], sum[20], pre[20];
int f[20][10][10];//表示一个长度为i的数,其中最高位是j,k这个数码一共出现的次数
//=============================================================
inline int read()
{
    int s=1, w=0; char ch=getchar();
    for(; !isdigit(ch);ch=getchar()) if(ch=='-') s =-1;
    for(; isdigit(ch);ch=getchar()) w = w*10+ch-'0';
    return s*w;
}
int qpow(int x,int y)//快速幂 
{
	int s = 1;
	while(y)
	{
	  if(y & 1) s *= x;
	  y >>= 1, x *= x;
	}
	return s;
}
int qmul(int x,int y,int mod)//快速乘 
{
	int s = 0;
	while(y)
	{
	  if(y & 1) s = (s + x) % mod;
	  y >>= 1, x = (x + x) % mod;
	}
	return s % mod;
}
void solve(int now)
{
	//初始化 
	memset(f, 0, sizeof(f));
	memset(sum, 0, sizeof(sum));
	for(lth = 0; now;) num[++ lth] = now % 10, now /= 10, sum[num[lth]] ++;
	
	for(int i = 0; i <= 9; i ++) f[1][i][i] = 1;//初始化 长度为1的数 
	for(int i = 2; i <= lth; i ++)//枚举 位数 
	  for(int j = 0; j <= 9; j ++)//枚举 最高位 
	  {
	  	for(int k = 0; k <= 9; k ++)//枚举 次高位 
	  	  for(int l = 0; l <= 9; l ++)//枚举 每个数字 
	  	    f[i][j][l] += f[i-1][k][l];//更新 
	  	f[i][j][j] += qpow(10, i - 1);//乘法原理, 更新最高位 出现次数, 计入贡献 
	  }
	
	//枚举 位数 <lth 的数, 必然满足条件, 不会越界, 直接计入答案 
	for(int i = 1; i < lth; i ++) //枚举 位数 
	  for(int j = 1; j <= 9; j ++)//枚举 最高位 
	    for(int k = 0; k <= 9; k ++)//枚举 数字 
	      sum[k] += f[i][j][k];
	
	//位数为 lth的数, 保证最高位 < 最高位 可以到达的最大值 
	for(int i = 1; i < num[lth]; i ++)//枚举最高位 
	  for(int k = 0; k <= 9; k ++)//枚举数字 
	    sum[k] += f[lth][i][k];
	
	//位数为lth的数, 第[i + 1,lth]高位 与 可以到达的最大值 相同 
	for(int i = lth - 1; i >= 1; i --)//枚举 不同的位数, 小于 可达到的最大值 
	{
	  for(int j = 0; j < num[i]; j ++)//枚举最高位 
	    for(int k = 0; k <= 9; k ++)//枚举数字
	      sum[k] += f[i][j][k];
	  
	  
	  for(int l = lth; l > i; l --)//由于保证  第[i + 1,lth]高位 与 可以到达的最大值 相同 , 
	    sum[num[l]] += num[i] * qpow(10, i - 1);//则需将 相同的数的贡献 计入答案 
	}
}
//=============================================================
signed main()
{
	T = read();
	while(T --) 
	{
	  a = read(), b = read();
	  solve(a - 1);
	  for(int i = 0; i <= 9; i ++) pre[i] = sum[i];
	  solve(b);
	  //前缀和相减求答案
	  int ans = 0;//求和 
	  for(int i = 1; i <= 9; i ++) ans = (ans + qmul(i,sum[i] - pre[i],mod)) % mod;
	  printf("%lld\n",ans); 
	}
}
posted @ 2019-10-27 08:35  Luckyblock  阅读(170)  评论(0编辑  收藏  举报