baekjoon

백준 9527번 : 1의 개수 세기 [자바]

Meluu_ 2025. 6. 5. 10:32

 

 

🧫 문제 분석

 

✔️ 출처

1의 개수 세기 골드 2

 

📖 문제

 

BigInteger 로 풀었는데 굳이 안써도 long타입으로 커버쳐지나보다.

2^i 인 수의 1의 개수를 누적합으로 구하는데 너무 큰 범위까지 구해서 그렇다.

10^16이하까지만 구하면됐는데 ...

 

포스팅한 이유는 사실 다른사람의 코드를 이해하기 위함이다.

 

https://tussle.tistory.com/1022

 

이 분의 풀이를 이해하고 정리해보겠다.
i = i번째 자리일때 최댓값까지의 1의 개수 
 
i = 2 이면 000~ 111 까지의 1의 개수를 구하는 것이다. 
i = 3 : 0000 ~ 1111까지의 1의 개수 
 
2^n일때 1의 개수
 
dp[n] = dp[n-1] * 2 + 2^n   if (n == 0) dp[0] = 1;
이런 점화식이 나오는데
예를 들어 n = 3일때
 
우리는 0000 ~ 1111 까지의 1의 개수를 구해야한다.
 
dp[2] = 000~111까지의 1의 개수가 구해져있다.
그리고 1000 이후로부터도 1을 제외하고 생각하면 000~111까지의 개수이다. 
따라서 dp[i-1] * 2 가 된다. 
 
여기에 1000 ~ 1111까지의 개수는 2^3개이므로, 앞에서 1을 제외한 1의 개수를 더해준다.
 
dp[i-1] * 2 + 2^i

 
이제 이 누적합을 가지고 문제를 풀면된다.
 
110101 이 입력되었을때 길이는 6이다. 
011111 일때 1의 개수는 알고 있다. dp[i-1]
100000 일때는 1의 개수는 1개이다. + 1
 
이제 나머지 를 구한다. 맨 앞 1을 제외한 나머지는 항상 맨 앞 1을 가지고 있기에 
[010101] 개를 가진다. 
 
100001
100010
100011
100100
100101
...
110101 
 
그리고 맨 앞 1을 없애고 다음 차례로 넘어간다. 이를 반복한다. 
 
10101 부터 다시 시작
 

 


🔅 문제 풀이 [ 내 풀이 ]

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;

public class Main {

    static BigInteger[] prefixOneCountSum;
    static long[] powerOfTwo;

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

        long n = Long.parseLong(st.nextToken());
        long m = Long.parseLong(st.nextToken());

        prefixOneCountSum = new BigInteger[64];
        powerOfTwo = new long[63];

        powerOfTwo[0] = 1;
        for (int i = 1; i < powerOfTwo.length; i++) {
            powerOfTwo[i] = powerOfTwo[i - 1] * 2;
        }

        precomputeOneCounts();

        BigInteger countM = countOnesUpTo(m);
        BigInteger countNMinus1 = countOnesUpTo(n - 1);

        System.out.println(countM.subtract(countNMinus1));
    }

    private static BigInteger countOnesUpTo(long target) {
        String binaryStr = Long.toBinaryString(target);
        int length = binaryStr.length();
        int prefixOneCount = 0;
        BigInteger totalCount = BigInteger.ZERO;

        for (int i = 0; i < length; i++) {
            if (binaryStr.charAt(i) == '1') {
                long partial = prefixOneCount * powerOfTwo[length - i - 1];
                totalCount = totalCount
                    .add(BigInteger.valueOf(partial))
                    .add(prefixOneCountSum[length - i - 1]);
                prefixOneCount++;
            }
        }

        return totalCount;
    }

    private static void precomputeOneCounts() {
        prefixOneCountSum[0] = BigInteger.ONE;
        prefixOneCountSum[1] = BigInteger.valueOf(2);

        for (int i = 2; i < prefixOneCountSum.length; i++) {
            int pow = i;
            int prefix = 0;
            BigInteger sum = BigInteger.ZERO;

            while (pow > 0) {
                pow--;
                sum = sum
                    .add(BigInteger.valueOf(prefix * powerOfTwo[pow]))
                    .add(prefixOneCountSum[pow]);
                prefix++;
            }

            sum = sum.add(BigInteger.ONE);
            prefixOneCountSum[i] = sum;
        }
    }
}

(이전까지 앞에 나온 1의 개수 * 해당 자리에서 1이 나올 수 있는 횟수) + 이전 자리 누적합

 

이 풀이의 경우

 

2^i 를 기준으로 누적합을 진행하였다.

 

2^0 = 1

2^1 = 2

2^2 = 5

2^3 = 13   1000 까지 

 

 

다른사람 풀이의 경우

 

i = 2이면 111

i= 3 이면 1111 까지 

 

🔅 문제 풀이 [ 다른 사람 풀이 ]

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
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 n = Long.parseLong(st.nextToken());
        long m = Long.parseLong(st.nextToken());

        dp = new long[55];

        // idx : idx자리의 위치가 최댓값일때 1로 꽉채울 경우 1의 개수 ex) idx = 3 -> 111 , idx = 0 -> 1, idx = 1 -> 11 까지 채우는 개수
        dp[0] = 1;
        for (int i = 1; i < dp.length; i++) {

            dp[i] = (dp[i - 1] << 1) + (1L << i); // dp[i] = dp[i-1] * 2  + 2^i
            // 왜 2^i냐면 앞에 새로운 1이 붙는데 기존 dp[i-1] 의 개수만큼 더 추가되면서 1이 하나씩 더 붙기때문
            // ex ) i = 2 -> 0~7
            // 전반 dp[i-1] 000, 001, 010, 011
            // 후반 dp[i-1] 100, 101, 110, 111
            // 총 4개의 수 즉 ,i^2개가 추가됨

            // dp[n-1] ~ dp[n]-1 까지는 n개의 추가 비트가 필요
            // 이유는 idx의 규칙때문, idx = 2일때는 7까지이므로 사실상 앞 idx의 1의 개수 -1 개까지를 구하기때문에 2^idx만큼 추가될 수 밖에 없음
            
        }

        long answer = calc(m) - calc(n - 1);
        System.out.println(answer);

    }

    // 1010101 일때
    // 1000000 까지 목표로 1의 개수를 구함
    // 0111111 개수 (dp[i-1]) +
    // 1000000 일때 1의 개수 (1개)
    // 1010101 - 100000000 일때 나머지
    // 0010101 은 앞 1이 고정이므로 생길때마다 1이 추가되었을 것임
    //따라서 n - 1L<<1 개

    private static long calc(long n) {
        long cnt = n & 1; // 첫째 자리
        int size = (int) (Math.log(n) / Math.log(2)); // 자릿수 구하기
        for (int i = size; i > 0; i--) {
            if ((n & 1L<<i) == 0) continue;

            cnt += dp[i - 1] + (n - (1L << i) + 1); // i번째 1까지 구하는게 dp[i-1] + i번째 1일때 1000.. 일때 1가지 + i번째 자리를 가질때 N - 1L<<1 의 수까지는 다 1을 고정적으로 가짐
            n -= (1L << i);
        }

        return cnt;
    }

}

❗ 오답노트 / 필요한 지식

  1.