PS/C++

[백준 1019] 책 페이지

siyamaki 2024. 6. 20. 13:01

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

 

1 ~ N 까지 수가 몇번이나 등장하는지 세는 문제

 

N의 자릿수가 올라가는 10의 계승마다 규칙을 찾는다. 0의 갯수가 자릿수마다 바뀌므로 규칙을 찾아보았다.

0의 갯수를 세기 편하게 자릿수가 1자리일땐 0 ~ 9, 2자리일땐 00 ~ 99 등으로 계산했다

규칙을 찾아보면 아래와 같다

 

자릿수 0의 갯수
1 1개(0)
2 1의자릿수(1개) + 10의자릿수(10개, 00 ~ 09지만 앞에서 00의 1의자리 0을 세었음) = 11개
3 1의자릿수 1개 + 10의자릿수 10개 + 100의자릿수 100개 = 111개
...  
N (int) 1.111111111 * 10의 N승

 

0의 갯수를 위 처럼 구하고 나머지 1 ~ 9의 개수는 몇번씩 세다보면 아래 표 처럼 된다

자릿수 1 ~ 9까지 등장하는 수의 갯수
1 1
2 20(1의 자릿수 10개, 10의자릿수 10개)
3 300(1의 자릿수 100개, 10의 자릿수 100개, 100의 자릿수 100개)
...  
9 900000000
10 10000000000

 

위 값을 arr배열에 저장한다.

계산할 때는 제일 높은 자릿수부터 제일 낮은 자릿수까지 수의 개수를 센다

 

ex)

32547의 경우

1만자리(k)의 맨 앞자리수를 계산해 0부터 9까지 수가 몇 번 반복되는지 센다(변수 r = 3)

00000 ... 09999, 10000 ... 19999, 20000 ... 29999 ->  0000 ~ 9999가 r번 반복함. 0000 ~ 9999의 각 자릿수는 4000개이므로 4000 * 3 = 12000씩 카운팅된다.

이후 k 자리의 숫자에서 0, 1, 2가 k개씩 더 나오니 각각 k씩 더해준다. 3은 2547번 등장하니 n % k를 통해

나머지 + 1(30000 때문에) 만큼 더해준다.

그리고 남은 나머지를 이용해 자릿수 nk값이 0보다 작아질 때 까지 반복 한다

 

그리고 위 계산에서 누적된 0의 갯수를 다시 빼준다.(코드에서는 계산 전에 미리 뺏다)

 

#include <iostream>
#include "cmath"

#define ll long long
using namespace std;
int N;
ll arr[11], res[10];
void getPart();
void calc(ll n);
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);
    cin >> N;
    getPart();
    calc(N);

    for(ll r : res) {
        cout << r << ' ';
    }
}
void getPart() {
    for(int i = 1; i <= 10; i++) {   // 자릿수
        ll k = (ll) pow(10, i - 1) * i;
        arr[i] = k;
    }
}
void calc(ll n) {
    int nk = (int) log10(n);
    double t = 1.11111111111111111;
    res[0] -= (int) (t * pow(10, nk));  // 자릿수에 맞춰 0의 갯수 빼줌
    while(nk >= 0) {
        ll k = (ll) pow(10, nk);
        ll r = (n / k) % 10; // 자리수
        for(int i = 0; i < 10 && r != 0; i++) {
            res[i] += arr[nk] * r;
        }
        for(int i = 0; i < r; i++) {
            res[i] += k;
        }
        ll remain = n % k;
        res[r] += remain + 1;
        nk--;
    }
}

 

댓글수0