문제풀기

한 달 돌아보기 (2024-01-23 ~ 2024-02-19)

lml 2024. 2. 19. 21:54

푼 문제들이 그렇게까지 흥미롭지도 않고 뭐해서 한 달이나 쌓게 되었다.

그래도 나름 채점 현황을 보니 백준에서 평균 하루에 하나는 풀었는지라 많이 모였다.

 

BOJ #10979 가넷이나 버는게 낫지 않아요? (P4)

 

시간상 2등으로 도착하게 되면서, COST는 최대를 챙겨야 하는 문제

다익스트라를 잘 돌리면 된다!

각 N개의 정점에 대해서 (걸리는 시간, 가넷의 수)를 고려하여 제일 나은 2개의 결과를 저장한다.

 

BOJ #1785 팀 사모예드의 신작 (P3)

 

적당히 재귀를 돌리면 충분히 잘 돌아간다.

즉, SOLVE(N, K)에 대해서 2, 3, 4, 5배짜리 지폐를 만든 뒤에 SOLVE(N / i, K - 1) + N % i의 최솟값 구하기

이때 중복해서 (N, K)에 들어가는 것은 map으로 잘 처리를 해준다.

실제로 랜덤하게 테스트해보면 1e5 이상 방문하는 경우가 없는데, 확실한 증명은 애매;

 

BOJ #1209 단조수열 만들기 (P4)

 

일단 귀찮으니 단조수열 B는 증가수열일 때만 고려하자. (감소일 때에도 똑같이 할 수 있다.)

claim) B에 들어갈 값들은 모두 A에 존재한다

pf) 처음으로 A에 없는 숫자인 B[i]에 대해 A[x] < B[i] < A[y]일 때, A[x] 혹은 A[y]로 바꾸는게 무조건 이득이다.

 

따라서 DP를 굴리는데, DP[i][j] = (i번째 숫자가 j일 때의 |A1 - B1| + ... + |Ai - Bi|의 최솟값)

O(N^2)으로 가능하다.

 

BOJ #12766 지사 배정 (D5)

 

우선 사실상 거리는 본부와 지사까지의 거리만이 중요하다!

따라서 dist[i] = (i번 지사에서 본부까지의 거리) + (본부에서 i번 지사까지의 거리) 라고 하자.

 

이제 dist에 대해서 정렬하자. (즉, i < j라면 dist[i] < dist[j])

당연하게도 제일 거리가 가까운 b개 외의 지사들은 모두 버려도 된다.

S(i)를 i번 지사가 들어가 있는 프로젝트의 크기라고 가정한다면, i < j일 때, S(i) > S(j)가 성립하게 된다.

dp[i][j] = (i개의 프로젝트에 대해 j개의 지사가 배정되었을 때, 최소 비용)이라고 정의하자.

단순하게 dp를 돌리면 O(s * b^2)이 된다. (각 dp[i][j]에 대해, [j + 1, j + sz]를 다음 프로젝트에 배정해보기)

 

이때 [j + 1, j + sz]를 배정할 때, 이 프로젝트는 기존의 i개 프로젝트보다 크기가 더 작아야 한다.

따라서 sz <= j / i가 성립하고, 위의 복잡도보다 짧은 O(s * blgb)가 된다.

 

BOJ #10905 Actual visible points (P2)

 

문제에서 주어지는 S와 조금 다르게 S를 정의하자

S[X] = {(x1, x2, ... xn) | 1 <= x1 <= ... <= xn = X, 각 x는 자연수}라고 하자.

우선 S[X]의 크기는 H(i, N - 1) = C(i + N - 2, N - 1)이다.

다만, 단순히 S값들의 합만 구한다면, 원점에서 보았을 때 가려지는 점들이 있어서 안된다.

 

sol1) O(M*X^(1/3)*sqrt(X)) (약수개수 = X^1/3라 가정)

cnt[X] = (S[X]에 속하지만, S[1], S[2], ... S[X - 1]에 의해 가려지지 않는 점의 수)라고 하자

그렇다면 cnt[X] = |S[X]| - sum(cnt[Y]) (단, Y는 X의 약수)

 

문제에서 주어진 S는 Xi중 하나라도의 약수가 되는 모든 d에 대해서 cnt[d]를 더하는 방식으로 구할 수 있다.

따라서 모든 d를 구해주고, 또 이 각각의 d에 대해 약수를 모두 구해서 cnt[d]를 구한 뒤 더해주면 된다.

약수의 개수를 대충 X^(1/3)이라 생각하면, 대략 M * X^(1/3)에 대해 모든 약수를 구해야 해서 위와 같은 복잡도

 

sol2) O(Xlg(X) + Msqrt(X))

가만보면 모든 cnt 값을 미리 구해두고 그냥 더하는게 더 싸게 먹힐 것 같다.

즉, for(int i = 1; i < MAXX; i++) cnt[i] = (cnt[i] + C(i + N - 2, N - 1)로 cnt[i] 를 구한 뒤,

for(int j = i * 2; j < MAXX; j += i) cnt[j] -= cnt[i]를 통해서 i가 약수인 모든 j에서 cnt[i]만큼을 빼주면 된다.

이렇게 해서 cnt를 모두 구하고 X의 약수가 되는 모든 d에 대해 합을 구해주면 된다.

 

sol3) O(Xlg(X))

위에 cnt 값을 구하는 과정을 잘 보면, j = X인 순간이 존재하는 경우에만 cnt[i]가 답에 더해지게 된다.

따라서 chk[i]를 만들어서, 모든 X에 대해 미리 chk[X] = 1 처리를 해주고, chk[i] |= chk[j]를 하자.

그 다음 맨 마지막에 cnt[i] * chk[i]의 합을 더하면, 딱 필요한 애들만 더해져서 답이 된다.

 

BOJ #12926 쉽게 제한된 메모리 (P2)

 

물론 내가 이미 PBS라는 개념을 알아서 그렇게 느끼는 것일수도 있지만,

문제를 풀다보면 자연스럽게 PBS에 도달하게 되는 것 같아서 좋다고 느꼈다.

 

각 쿼리는 이분탐색으로 답을 구할 수 있다. O(Q * lg(MOD) * N) 

어떤 mid에 대해서 X[i] <= mid인 경우의 수(=cnt[mid])가 q보다 큰지 작은지 확인하면 되기 때문이다.

7초에 희망을 걸고 제출해보면 시간이 부족하다.

 

그러면 적당히 자연스럽게 굳이 X를 모두 구하는 O(N)짜리 과정을 이렇게 여러번 반복해야하나 싶어진다.

동시에 이분탐색을 돌릴 수도 있을 것 같다는 생각...

L[Q]와 R[Q]를 관리하면 된다. 

q[i] < q[j]라면, 당연히 cnt[i] <= cnt[j]임을 이용하여,

어떤 X[k]에 대해, mid 값 중에서 lower_bound에 1을 더해두고, 마지막에 누적합 하듯이 cnt를 구할 수 있다.

 

BOJ #16418 Inversions (D4)

 

claim) 비어있는 자리들은 모두 감소수열이다

pf) ? ? ? x ? ? ? y ? ? ?인데 x < y라고 가정하자. 이때 (x, x), (y, y) 중 하나가 반드시 이득이다.

따라서 다음과 같은 dp를 고려하자

dp[x][p] = (숫자가 정해지지 않은 x번째 자리에 대해, 이를 p로 정했을 때 최대 inversion의 수)

 

dp[x][p]를 구할 때에 단순히 생각하면 dp[x - 1][q]를 고려한다. (q >= p)

만일 q > p라면 dp[x][p]를 구하기가 쉽다. 

왜냐하면 앞에 있는 모든 "missing" 들과 p와의 관계가 inversion이 되기 때문이다.

하지만 dp[x - 1][p]에서 dp[x][p]로 넘어갈 때에는 몇 개의 missing이 p이고, inversion이 안되는지 모른다!

 

따라서 우리는 dp[x][p]를 구할 때 사실은 dp[y][q] (y < x, q > p)를 고려해야함을 알 수 있다...

즉 dp[x][p] = MAX(dp[y][q] + y * (x - y)) + (정해진 자리들과의 inversion)

단순하게 모든 y < x인 y를 모두 고려하면 너무 느려져서 TLE를 받을 것이다.

위의 식을 잘 보면 x * y - y + dp[y][q]임을 알 수 있고, 이것은 CHT가 가능한 형태이다.

따라서 dp[x][p] = cht[p + 1].query(x) + (정해진 자리들과의 inversion)로 구할 수 있고, 

이후에는 cht[p]에 (x, -x + max(dp[x][q]))라는 직선을 더해주면 된다.

기울기가 단조 증가하는 순서로 더할 수 있기에 선형 CHT이고, 로그 단위로 굴러가는 구조체들은 아마도 터질 것이다.

 

BOJ #14207 약수 도로 (P4)

 

흥미로운 BFS 문제

우선 A[i]가 S의 약수인 모든 i는 거리가 1이라고 생각할 수 있다.

어떤 cur번 도로가 거리가 x라고 생각한다면,

(A[cur] 배수) -> (B[cur] 배수) = (A[nxt] 배수) -> (B[nxt] 배수)로 넘어갈 수 있다면, nxt번 도로는 거리가 x + 1이다.

B[cur]과 A[nxt]의 최소공배수가 N이하인지 확인하여 nxt의 거리를 구해줄 수 있다.

B[i]가 T의 약수인 모든 i 중에서 거리가 제일 가까운 것을 구하면 된다.

 

BOJ #22346 일이 이어져야 좋다 (P1)

 

정해는 KOI 사이트에 있으니 확인해 보도록... (내 풀이보다 훨 빠르게 돌아감)

세그트리나 분할정복에 익숙한 편이라면, 사실 그리 떠올리기 어렵지는 않을 것 같다.

 

적당히 답이 되는 크기가 SZ 정도라고 생각해보자.

이때 pow(2, bit + 1) > SZ인 적당한 bit에 대해서, SZ라는 범위 내에는 2^(bit + 1)이라는 자리가 한 두개 들어간다.

그렇다면, 어차피 저 자리 외에 나머지는 계속 반복적으로 등장한다는 뜻이다. (S1 0 S2 꼴이거나 S1 1 S2 꼴)

그러면 적당히 bit번째 숫자가 u 이후에 나타나는 자리를 nxt[u][bit], 또 그 다음은 nnxt[u][bit]이라고 하자.

이 자리들에 대해서, 왼쪽으로 0이 k개 있는 것부터, 오른쪽으로 0이 k개 있는 것까지 한 칸씩 밀며 모두 확인하자.

즉, 예를 들어 S13을 생각하면 11101111111에서 한 칸 밀면 11111110111가 된다.

이렇게 모든 bit에대해 확인한 모든 0이 k개 있는 부분문자열에 대해서 제일 긴 것의 길이를 구하면 된다.