数位统计DP - 计数问题

142 阅读2分钟

计数问题

  • 问题背景

Snipaste_2023-03-28_14-56-14.png

  • 分析,通过count(int n, int x)来算出1 ~ n中出现了多少次xx0取到9

Snipaste_2023-03-28_14-54-52.png

  • 对于七位数n = a b c d e f g,其中a ~ gn上的每一位数,现在我要计算d这个位置上出现了几次x

    • 情况1:当a b c0 0 0 ~ a b c-1 时,这时候d这个位置上的x出现了abc * 1000
    • 情况2:当a b ca b c
      • 情况2.1:若d < x,这时候d这个位置上的x出现了0
      • 情况2.2:若d = x,这时候d这个位置上的x出现了efg + 1
      • 情况2.3:若d > x,这时候d这个位置上的x出现了1000
    • 注意,如果x取的是0,那么情况1中的a b c就只能从 0 0 1 ~ a b c-1中取,因此这种情况还要减去1000
  • 上面只是例举了count(int n, int x)中的一个情况,下面进行抽象描述整个计算的过程,计算1 ~ n中一共出现了多少次x

    • 长度为size容器num中从小到大存放着n的每一位数字,即num[0]存放个位,num[size-1]存放最高位 ;res存计算结果,即1 ~ n中一共出现了多少次x
    • 定义get(vector<int> num,int l,int r),获取num中区间[l, r]表示的数,例如n = a b c d e f gget(num, 4, 6) = efg
    • 定义power10(int i),返回10^i^
    • 循环从最高位i = size - 1 - !x 开始,i >= 0,每次i--。为什么要减去!x?如果x0,意味着计算最高位出现0的次数,显然最高位不可能出现0,因此减去了!x防止这种情况的发生
      • 情况1:res += get(num, i + 1, size - 1) * power10(i);
      • 注意点:if (x == 0) {res -= power10(i)}
      • 情况2.1:可以省略
      • 情况2.2:if (num[i] == x) {res += get(num, 0, i - 1) + 1};
      • 情况2.3:else if (num[i] > x) {res += power10(i)}
    • return res;
  • 代码

    • public static int count(int n, int x) {
          ArrayList<Integer> num = new ArrayList<>();
          while (n != 0) {
              num.add(n % 10);
              n /= 10;
          }
          n = num.size();
          int res = 0;
          for (int i = n - 1 - (x == 0 ? 1 : 0); i >= 0; i--) {
              //情况1
              res += get(num, i + 1, n - 1) * Math.pow(10, i);
              //注意点
              if (x == 0) {
                  res -= Math.pow(10, i);
              }
              //情况2.2
              if (num.get(i) == x) {
                  res += get(num, 0, i - 1) + 1;
              } else if (num.get(i) > x) {  //情况2.3
                  res += Math.pow(10, i);
              }
          }
          return res;
      }
      

练习

01 计数问题

  • 题目

Snipaste_2023-03-28_17-55-03.png

  • 题解
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        while (true) {
            String[] str1 = br.readLine().split(" ");
            int a = Integer.parseInt(str1[0]);
            int b = Integer.parseInt(str1[1]);
            if (a == 0 && b == 0) {
                break;
            }
            if (a > b) {
                int t = a;
                a = b;
                b = t;
            }
            for (int i = 0; i < 10; i++) {
                //区间和
                pw.print(count(b, i) - count(a - 1, i) + " ");
            }
            pw.println();
        }
        pw.close();
        br.close();
    }

    public static int count(int n, int x) {
        ArrayList<Integer> num = new ArrayList<>();
        while (n != 0) {
            num.add(n % 10);
            n /= 10;
        }
        n = num.size();

        int res = 0;
        for (int i = n - 1 - (x == 0 ? 1 : 0); i >= 0; i--) {
            //情况1
            res += get(num, i + 1, n - 1) * Math.pow(10, i);
            //注意点
            if (x == 0) {
                res -= Math.pow(10, i);
            }
            //情况2.2
            if (num.get(i) == x) {
                res += get(num, 0, i - 1) + 1;
            } else if (num.get(i) > x) {  //情况2.3
                res += Math.pow(10, i);
            }
        }
        return res;
    }

    //获取num中区间[l, r]表示的数,例如 n = a b c d e f g  get(num, 4, 6) = efg
    public static int get(ArrayList<Integer> num, int l, int r) {
        int x = 0;
        for (int i = r; i >= l; i--) {
            x = x * 10 + num.get(i);
        }
        return x;
    }
}