[백준] 9527: 1의 개수 세기 - JAVA

https://www.acmicpc.net/problem/9527

풀이

자연수 A부터 B까지 모든 수에 대해 이진수로 표현했을 때 1의 개수를 구해야한다.

자연수의 범위는 10의 16제곱까지였다.

2의 54제곱이 10의 16제곱을 넘어서 이진수로 54자리수까지 담는 배열을 만들었다.

dp[i] = (dp[i - 1] << 1) + (1L << i);

배열의 점화식은 위와 같다. i자리수는 i-1자리수의 모든 경우의 앞에 1을 붙이면 되기 때문이다.

B까지의 개수에서 A-1까지의 개수를 빼주면 정답이다.

하지만 예를 들어 자연수 14가 주어지면 3자리까지의 개수를 더하고 나머지를 더 구해줘야 한다.

이진수들을 써본 결과 규칙을 찾았고 다음과 같은 식으로 계산할 수 있었다.

return dp[digit - 1] + diff + count(n - num);

digit는 n보다 작은 이진수의 자리수 이고, diff는 n보다 작은 2의 제곱수와 n의 차이이다.


메모리: 14244KB

시간: 124ms

언어: Java 11

코드

import java.io.*;
import java.util.*;

public class Main {
    static long[] dp;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        long A = Long.parseLong(st.nextToken());
        long B = Long.parseLong(st.nextToken());

        dp = new long[55];
        dp[0] = 1;
        for (int i = 1; i < 55; i++) {
            dp[i] = (dp[i - 1] << 1) + (1L << i);
        }

        long ans = count(B) - count(A - 1);

        System.out.println(ans);
    }

    private static long count(long n) {
        if (n == 0 || n == 1) {
            return n;
        }

        int digit = 0;
        long num = 1;
        while (num * 2 <= n) {
            num *= 2;
            digit++;
        }

        long diff = n - num + 1;

        return dp[digit - 1] + diff + count(n - num);
    }

}