두 수를 입력받아 두 수 사이의 모든 수를 2진수로 표현했을때 1의 개수를 구하는 문제이다.
일단, 문제 접근을 1 부터 어떤 숫자까지의 범위에서 1의 개수를 구하는 것으로 생각하고 시작했다.
MSB가 2^k일때, 그 이진수 숫자가 표현할 수 있는 모든 숫자의 1의 수를 d[k]라고 하자. (k 는 0부터 시작)
d[k]는 d[k-1] + d[k-1] + 2^k로 표현 가능하다. 첫번째 d[k - 1]는 k번째 bit 꺼져있을때 밑 자리 애들의 합이고, 두번째 d[k - 1]은 k 번째 bit가 켜져있을때 밑 자리 애들의 합이다. 그 뒤 2^k는 k번째 bit가 켜졌을 때의 개수를 합하였다.
이 방식으로, d[k] 배열을 만들 수 있다. 이 배열을 완성시키고 나서 1 ~ 입력한 수 까지의 1 개수를 구하는 함수를 실행시켜 답을 얻을 수 있다.
그럼 이제 우리는 MSB 기준으로 1의 개수를 셀 수 있다.
이제 일반 수 x를 기준으로 거기서 1의 개수를 어떻게 세는지 보자.
그 수를 2진수로 표현하면, 거기서도 MSB를 찾을 수 있다. 그 MSB가 j 라고 하면, 우리는 d[j - 1] 로 밑 범위에서 1의 개수를 구할 수 있다.
그리고, 현재 j번째 비트에서의 1의 개수를 셀 수 있다. (x - 2^j + 1). 여기까지 더해준 다음, x 에서 2^j를 빼고 j를 한 비트 내려준다.
이걸 0번째 비트까지 반복하면 된다.
+ 0번째 비트는 짝수인지 홀수인지 판별해서 더해주기만 하면 된다.
#include <iostream>
long long calc1Cnt(long long x, long long d[])
{
long long ret = 1 & x;
for (long long i = 54; i > 0; i--)
{
if (x & (1LL << i))
{
ret += d[i - 1] + (x - (1LL << i) + 1);
x -= (1LL << i);
}
}
return ret;
}
int main()
{
long long d[55];
long long a, b;
d[0] = 1;
for (long long i = 1; i < 55; i++)
{
d[i] = d[i-1] * 2 + (1LL << i);
// std::cout << d[i] << " ";
}
std::cin >> a >> b;
std::cout << calc1Cnt(b, d) - calc1Cnt(a - 1, d);
}
처음 접근은 맨땅에 헤딩하기었는데, 루프돌면서 각 비트 마스킹해서 참이면 한개씩 올려줬다. 좀 개수 써보면서 접근할걸...
주말동안 진짜 정신없었다. 이제 마음챙기면서 장기전을 준비해야겠다.
오늘도 평온한 하루가 되길. 슨민.
'짜잘한 기록' 카테고리의 다른 글
백준 16161 가장 긴 증가하는 팰린드롬 부분수열 (0) | 2021.09.15 |
---|---|
백준 1644 소수의 연속합 (0) | 2021.09.14 |
백준 1987 알파벳 (0) | 2021.09.11 |
백준 3109 빵집 (0) | 2021.09.10 |
백준 1167 트리의 지름 (0) | 2021.09.09 |