分享

[SCOI2009] windy 数

 昵称71011036 2020-08-10

题面

题目描述

不含前导零且相邻两个数字之差至少为 22 的正整数被称为 windy 数。windy 想知道,在 aa 和 bb 之间,包括 aa 和 bb ,总共有多少个 windy 数?

输入格式

输入只有一行两个整数,分别表示 aa 和 bb。

输出格式

输出一行一个整数表示答案。

样例#1        样例#2

输入:1 10         输入: 25 50
输出: 9          输出:20
数据范围:
对于所有的数据,满足 1≤a≤b≤2×1091≤a≤b≤2×109

思路:

首先应该想到的就是数位DP,数位DP就是用来解决形如“在 ll 到 rr 的区间里有多少个数满足题目要求”的问题。

这个题题面的意思大概就是像 183,691183,691 这样的相邻数字之间的差是 22 的数是windy数。

暴力的话显然是不行的,这道题的数据范围很大,从 aa 枚举到 bb 时间复杂度可想而知。

那用数位DP的话怎么做呢?显然这个题有那么点区间DP的意思,但是区间DP并不能解决此题(状态设计出来了没法高效地转移)。

数位DP的状态设计就是 f[i][j]f[i][j] 表示前ii位中最高位为 jj 的windy数的个数。显然 f[i][j]=∑|k−j|≥2f[i−1][k]f[i][j]=∑|k−j|≥2f[i−1][k] (这个是因为此类DP的原理其实就相当于一位一位地往上加数)

状态和转移方法都有了,那该怎么得出题目所要求的东西呢?

我们可以考虑利用前缀和的思想得出该结果。直接求区间显然不怎么现实,那我们就可以分别求出 1−(l−1)1−(l−1) 和 1−r1−r 区间的和,再相减就可以得出结果。

那又怎么求出 1−x1−x 区间的和呢?这里直接求又是不行的。考虑分类讨论。以 x[i]x[i] 表示 xx 这个数的第 ii 位, cntcnt 表示 xx 的位数。

一、求出 cnt−1cnt−1 位的windy数的个数

因为我们要讨论 1−x1−x 这个区间,所以 cnt−1cnt−1 位的windy数是完全被包括在这个范围内的,因此可以直接求解

二、求出有 cntcnt 位且最高位小于 x[0]x[0] 的windy数。

这些windy数显然也是也是在上述范围内的,可以直接累加到答案中。这部分答案即为: ∑1≤x[0]≤9f[cnt][x[0]]∑1≤x[0]≤9f[cnt][x[0]]

三、类比二、中的过程。

依次求出有 cnt−1cnt−1 位、最高位为 x[1]x[1] 的windy数,有 cnt−2cnt−2 位、最高位为 x[2]x[2] 的windy数…………有 11 位、最高位为 x[cnt]x[cnt] 的windy数。每向下推进一位,前面的数字(更高位的数字)就已经固定,即为 xx 这个数的前tt位。
这里需要注意控制一个边界条件。就是当以 x[t]x[t] 为最高位时,若 |x[t]−x[t+1]|<2|x[t]−x[t+1]|<2 ,那么最高位为 x[t]x[t] ,次高位为 x[t+1]x[t+1] 的windy数不存在,直接退出。

Code:

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<algorithm>
  4. #include<cstring>
  5. #include<cmath>
  6. typedef long long ll;
  7. int a, b;
  8. int f[11][11];
  9. inline int read(void){
  10. int f = 1, x = 0;char ch;
  11. do{ch = getchar();if(ch=='-')f = -1;} while (ch < '0' || ch > '9');
  12. do{ x = x * 10 + ch - '0';ch = getchar();} while (ch >= '0' && ch <= '9');
  13. return f * x;
  14. }
  15. inline int _abs(int x) { return x <= 0 ? -x : x; }
  16. inline void _init(void){
  17. for (int i = 0; i <= 9;++i)
  18. f[1][i] = 1;
  19. for (int i = 2; i <= 10;++i){
  20. for (int j = 0; j <= 9;++j){
  21. for (int k = 0; k <= 9;++k)
  22. if(_abs(j-k)>=2) f[i][j] += f[i - 1][k];//预处理出所有的windy数的个数
  23. }
  24. }
  25. return;
  26. }
  27. inline int calc(int k){
  28. if(k==0) return 0;
  29. int res = 0, cnt = 0, num[11] = {0};
  30. while(k){
  31. num[++cnt] = k % 10;
  32. k /= 10;
  33. }//将读入的数分解
  34. for (int i = 1; i < cnt;++i){
  35. for (int j = 1; j <= 9;++j)
  36. res += f[i][j];//求出cnt-1位的windy数的个数,可以直接累加
  37. }
  38. for (int i = 1; i < num[cnt];++i)
  39. res += f[cnt][i];//处理出所有cnt位、且最高位小于读入数字最高位的windy数的个数
  40. for (int i = cnt - 1; i >= 1;--i){//最难搞的一块,处理其余的情况
  41. for (int j = 0; j < num[i];++j)
  42. if(_abs(j-num[i+1])>=2) res += f[i][j];
  43. if(_abs(num[i+1]-num[i])<2) break;//不要忘了判断是否合法
  44. }
  45. return res;
  46. }
  47. int main(){
  48. a = read(), b = read();
  49. _init();
  50. printf("%d\n", calc(b) - calc(a - 1));//前缀和处理
  51. // std::cout << '\n' << calc(b) << ' ' << calc(a - 1);
  52. return 0;
  53. }

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多