Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

lmlmlm

Codeforces Round 875 (Div. 1) 본문

문제풀기

Codeforces Round 875 (Div. 1)

lml 2023. 5. 29. 15:45

https://codeforces.com/contest/1830

 

1D가 상당히 어렵게 나왔다.

1B에 TLE, MLE를 각각 한 번씩 받고, 1C를 정직하게(?) 구현해서 너무 늦게 제출했다.

사실 어지간히 B, C를 빠르게 AC 받았어도 곧바로 레드는 못 갔기에 그렇게까지 억울하지는 않긴 하다.

 

1A. Copil Copac Draws Trees

 

루트가 정해진 트리이기에 u - v라는 간선은 cur - par간의 간선이라고 생각해도 된다.

각각의 정점 i에 대해 chk[i]를 i가 트리에 추가된 회차라고 하자.

1) cur - par 간선이 par - (parent of par)보다 먼저 나온다면 -> chk[cur] = chk[par] + 1

2) cur - par 간선이 더 나중에 나온다면 -> chk[cur] = chk[par]

따라서 루트 노드인 0번 노드부터 dfs를 돌면서 chk을 위에서부터 순서대로 채워주면 모든 chk값을 얻을 수 있다.

chk값의 최댓값이 답이 된다.

 

1B. The BOSS Can Count Pairs

 

관찰 : 왜 굳이 a[i], b[i] 제한을 N으로 잡았을까?

 

a[i] * a[j] = b[i] + b[j]

1) a[i]가 SQRT보다 크다 : b[i] + b[j] <= 2N이므로 a[j] <= 2SQRT이다.

-> 정해진 a[i]에 대해서 b[j] = a[i] * a[j] - b[i]여야만 (i, j) 쌍이 가능하다.

-> 따라서 모든 a[j] <= 2SQRT인 j에 대해서 chk[a[j]][b[j]]++를 해두었다고 가정하자.

-> 그러면 a[i] >= SQRT인 i에 대해서 0 ~ 2SQRT인 x에 대해서 ans += chk[x][a[i] * x - b[i]] 를 하면 된다.

 

2) a[i]가 SQRT보다 작다 : 어떤 a[j]에 대해서 SQRT보다 작은 모든 a[i]를 고려해주면 된다.

-> 정해진 a[j]에 대해서 b[i] = a[i] * a[j] - b[j]여야만 (i, j) 쌍이 가능하다

-> 따라서 모든 a[i] <= SQRT인 i에 대해서 chk[a[i]][b[i]]++를 해두었다고 가정하자

-> 그러면 모든 j에 대해서 0~SQRT인 x에 대해서 ans += chk[x][x *  a[j] - b[j]]를 하면 된다.

 

모든 i, j에 대해서 최대 2SQRT 정도의 iteration을 거치므로  NsqrtN의 복잡도를 가지게 된다.

 

C. Hyperregular Bracket strings

 

Bracket 질을 할 때의 기본은

a) 길이 2N인 Bracket sequence의 종류는 Catalan(N)이다.

b) '(' = 1, ')' = -1이라고 했을 때 각각의 지점에 대해 prefix sum을 구할 수 있다. -> 이때 psum[i] >= 0, psum[2N] = 0

 

event 1) 어떤 [L, R), [l, r)에 대해서 L <= l <= R <= r로 겹친다고 가정하자  (뒤쪽이 열린 구간임에 유의)

-> 만일 psum[l] > psum[R]이라면 [l, r]은 psum[R] - psum[l] < 0이기에 regular bracket이 될 수 없다.

-> 만일 psum[l] < psum[R]이라면 [L, R]은 psum[l] - psum[L] (=psum[R]) < 0이기에 regular bracket이 될 수 없다.

-> 따라서 반드시 psum[L] = psum[l] = psum[R] = psum[r]이 된다.

-> 그러면 경우의 수는 Catalan((l - L) / 2) * Catalan((R - l) / 2) * Catalan((r - R) / 2)가 된다.

 

event 2) 만일 [L, R), [l, r)에 대해서 L <= l <= r <= R로 겹친다고 가정하자

-> 그렇다면 [l, r)에서 따로 regular bracket을 만들고 [L, l) + [r, R)이 regular bracket을 만들면 된다.

-> 그러면 경우의 수는 Catalan( (R - L) / 2 - (r - l) / 2) * Catalan((r - l) / 2)가 된다.

-> 이때 이것은 [L, L + r - l), [L + r - l, R)과 값이 동일하다.

 

event 2과 event 2를 통해서 구간들을 잘 관리하면 되겠다는 생각을 할 수 있다.

 

Sol1) 멍청하고 정직한 풀이 (내 풀이)

Stack처럼 구간들(set으로 관리)을 쌓아간다는 생각을 하자.

예를 들어 만일 set1에 {x1, x2, x3, x4}가 있다면 psum[x1] = psum[x2] = psum[x3] = psum[x4]이라는 뜻이다.

그러면 이것보다 위에 또 다른 set2 {y1, y2, y3, y4}가 stack되어 있다고 가정하자.

-> 그렇다면 이것은 set2의 y1~y4가 x[i] <= y[1] < y[4] < x[i + 1]로 하나의 구간에 함께 포함된다는 의미이다.

 

이렇게 Stack을 정의했다면 구간 [u, v)를 순서대로 정렬해서 넣기 시작하자.

1) 새로운 구간 [u, v)가 들어왔을 때 [u, v)가 y[i] <= u < v <= y[i + 1]을 만족한다.

-> 그렇다면 [u, v)는 하나의 구간 내에 함께 포함되므로 위쪽에 set3 = {u, v}를 하나 더 stack시키면 된다

2) 새로운 구간 [u, v)가 들어왔을 때 y[i] <= u <= y[i + 1] < v를 만족한다.

-> 이러면 u, v가 set2에 포함된다는 뜻이다.

-> 따라서 set2에 추가하여 {y1, y2, y3, y4, u, v}가 된다.

-> 이때 만약에 x[i] <= u <= x[i + 1] < v를 만족한다면 set2와 set1이 겹쳐지므로 event 1에 의해서 모두 psum이 같아진다.

-> 따라서 set1에 set2가 병합되어 set1 = {x1, x2, x3, x4, y1, y2, y3, y4, u, v}가 된다.

3) 새로운 구간 [u, v)가 들어왔을 때 Max(y) < u를 만족한다.

 -> 구간들은 모두 정렬되어 있었으므로 set2에 더 이상 추가될 수 있는 구간들이 없다.

-> event 2에 의해서 우리는 [L, R), [l, r)은 [L, L + r - l), [L + r - l, R)과 계산값이 동일함을 확인할 수 있다.

-> 따라서 set 2의 원소들이 x1 <= y[1] <= y[4] < x[i + 1]이라고 가정한다면

-> set 1 = {x1, y2 - y1 + x1, y3 - y1 + x1, y4 - y1 + x1, x2, x3, x4}로 바꾸어버려도 계산값이 동일하다.

-> 이때 y4 - y1 + x1 <= y4이기에 x1 ~ y4 - y1 + x1은 후에 다른 구간들에 영향을 끼치지 않으니 이래도 된다.

 

맨 처음에 [0, N)을 stack의 바닥에, 맨 마지막에 [N + 2, N + 2)라는 가상의 구간을 넣으면 마지막에 set이 하나 남는다.

맨 마지막의 set에 {z1, z2, ... zk}라면 답은 Catalan((Z2 - Z1)/2) * Catalan((Z3 - Z2)/2) * ...이 될 것이다.

위의 풀이를 보면 set을 합치는 과정이 여럿 보이는데, 작은거 큰거 테크닉을 써야 TLE를 받지 않을 것이다.

사실 애초에 정해가 이게 아니다 보니 저격 테케가 있을지는 잘 모르겠다.

https://codeforces.com/contest/1830/submission/207653111

결과물만 놓고 보면 그렇게까지 구현이 더럽지는 않다.

 

Sol2) XOR hashing을 통한 이쁜 구현 (정해)

위의 관찰을 보면 다음과 같은 생각을 할 수 있다.

event 1)에서는 [L, l), [l, R), [R, r)이 각각 하나의 구간을 이루고

event 2)에서는 [L, l) + [r, R)이 하나의 구간을, [l, r)이 하나의 구간을 이룬다고 생각을 할 수 있다.

 

그러면 적당히 랜덤한 숫자 x1을 L, R에, x2를 l, r에 부여한다고 생각하자.

-> 이후 prefix_XOR[i]을 통해서 각 숫자 1 ~ N에 대해서 적절한 해시값을 구할 수 있다.

-> 만약 prefix_XOR[i] = prefix_XOR[j]라면, i와 j가 같은 하나의 구간에 속한다고 생각을 할 수 있다.

len[i] = (prefix_XOR[j] = i인 j의 개수)라고 정의한다면

ans = Catalan(len[0] / 2) * Catalan(len[1] / 2) * ...의 방식으로 답을 구할 수 있다.

https://codeforces.com/contest/1830/submission/207713705

 

사실 XOR hashing이라는 것을 알지도 못했지만 

1) [L, l) + [r, R)이라고 생각하는게 아니라 [L, L + r - l), [L + r - l, R)으로 바꾸는 생각했다는 점

2) l, r < n이라는 조건을 굳이 주었다는 점

을 전혀 인지하지 못한 것도 큰 것 같다.

사실 내 풀이도 Catalan(N = 1e9)은 빠르게 구할 수 없기 때문에 l, r이 커지면 답을 못 구하는 것은 매한가지긴 하다.

 

1D. Mex Tree

 

보자마자 당연히 해볼만한 생각은 연결된 u - v에 대해서 반드시 val[u] != val[v]가 되도록 배정하는 것이다.

관찰을 해보면 이렇게 되면 (val[i] = 0인 i의 개수) + n * (n - 1)이 됨을 알 수 있다. (= 예제는 이 값이 답이다)

그러면 이제 val[u] = val[v]로 만들어서 MEX(u, v)를 희생시키는 생각을 할 수 있다.

예를 들어 (1, 3), (1, 4), (1, 5), (1, 6), (1, 2), (2, 7), (2, 8), (2, 9), (2, 10) 정도면 val[1] = val[2] = 1을 주는게 이득이다.

 

이것을 조금만 더 일반화 한다면

dp[cur][i][j] = (cur의 val값은 i이고, cur에서 부터 val이 i값인 노드들로만 연결된 노드의 개수가 j일 때 최댓값)이면

A - B를 연결시킨다고 가정했을 때 dp[A] 값과 dp[B] 값을 합쳐서 새로운 dp[A]를 구할 수 있다.

-> ex) nxt_dp[0][i + j + 1] = dp[A][0][i] + dp[B][0][j] - (i + 1) * (j + 1) + subtree[A] * subtree[B]

이것을 단순히 하면 A - B 연결은 N번, nxt_dp를 만드는 과정은 subtree[A] * subtree[B]이니 대충 O(N^3)이다.

ㅡㅡㅡ콘테 중 생각한 부분

 

자 저 풀이는 완벽하게 맞고 optimization을 하면 된다.

0) 위의 풀이는 이미 O(n^2)이다.

sub[i] = (i의 subtree의 크기), calc[i] = (i보다 아래의 tree dp를 하면서 한 계산의 횟수)라고 가정하면 위의 dp에서는

calc[cur] <= calc[child1] + calc[child2] + .. + sum( sub[cur] * sub[child] )가 된다.

 

가정) calc[i] <= sub[i] ^ 2 : 일단 calc[leaf] = 0이니 처음에는 성립한다.

-> calc[child1] <= sub[child1] ^ 2이므로 위의 식을 다시 쓰면

-> calc[cur] <= sub[child1]^2 + ... + sum (sub[cur] * sub[child] ) <= (sub[c1] + sub[c2] + ...) ^ 2 = sub[cur]^2 

따라서 위의 tree dp에서의 계산은 O(n^2) 이하이다.

https://codeforces.com/blog/entry/100910 의 #7 참고

 

1) 관찰 : 만일 같은 값인 노드들이 sqrt(n)개가 함께 연결되어 있다고 가정하자

-> 그렇다면 이미 여기에서 sqrt(n) * sqrt(n) = n 정도의 손해를 봤기 때문에 답이 벌써 n^2 보다 작다

-> 따라서 dp[cur][i][j]에서 j 값이 sqrt(n)을 넘지 않는다는 것이다.

-> 이게 시간 복잡도를 O(NsqrtN)으로 바꾸어 줄거라는 믿음의 도약을 하면 TLE를 받지 않는다.

 

엄밀한지는 모르겠는 증명

a) 어떤 A, B의 subtree가 합쳐질 때 A, B 중 subtree가 하나라도 sqrt보다 큰 경우 (WLOG A > sqrt)

-> 이때 어차피 A와 B가 합쳐지면 사이즈가 sqrt로 유지되니까 B는 그냥 사라진다고 생각할 수 있다.

-> 모든 원소는 딱 한 번만 사라질 수 있다.

-> 따라서 최대 N번 사라질 수 있고, 최대의 계산은 NsqrtN이다.

b) A, B가 모두 sqrt보다 작은 경우

-> 최초로 어떤 subtree의 크기가 SQRT가 되는 상황을 생각해보자.

-> 그렇다면 당연히 이게 만들어질 대 subtree가 1개씩 커지는 것이 계산량이 제일 많다.

-> 즉 calc[cur] <= sub[cur] + (sub[cur] - 1) + .. + 1 <= (sub[cur])^2 <= N 이다.

이때 최초로 subtree가 sqrt보다 커지는 상황은 sqrt번 이하이고, 따라서 b의 계산도 NsqrtN 이하.

 

2) 문제의 MLE limit이 256MB이다.

-> 위쪽의 관찰을 보면 j값은 항상 subtree[cur]보다 작다는 것을 알 수 있다.

-> 따라서 전위 순회를 하였을 때 주는 값이 in이라고 가정한다면

-> dp[cur][i][j]로 저장하는 대신에 dp[i][ in[cur] + j ]에 저장하더라도 아무 문제가 생기지 않는다.

-> cur과 child가 같은 메모리를 공유하지만 child가 cur에게 합쳐지고 나서야 그 메모리를 사용하기에 상관없다.

 

이상 간단한 Tree dp인데 잘 최적화시키는 문제였다.

https://codeforces.com/contest/1830/submission/207718827

위의 코드는 정말 대충 만들어서 왜 통과하는지는 모르지만 통과한다.

 

'문제풀기' 카테고리의 다른 글

SWERC 2020  (0) 2023.06.06
Codeforces Round 877 (Div. 2)  (0) 2023.06.05
AtCoder Regular Contest 161  (1) 2023.05.29
NWRRC 2022  (1) 2023.05.28
2022 ICPC Asia Taiwan Online Programming Contest  (0) 2023.05.24
Comments