HYEA cup H - Too Many Traps 풀이

2019. 3. 11. 10:27알고리즘 문풀/BOJ 연습

문제 링크


공식 에디토리얼


1년 가까이 틀려 있었던 문제다. 에디토리얼을 읽고도 이해를 못해서... 내가 이해한 대로 다시 써보려고 한다.


문제 요약


수직선상에 MM개의 이불이 있고, 혜아는 원점 00에 서 있다.

혜아는 NN개의 신발을 가지고 있는데, ii번째 신발을 사용하면 AiA_{i}만큼 +방향으로 갈 수 있다.

M<NM < N이 성립할 때, 모든 신발을 한 번씩만 사용하여 이불에 닿지 않고 A1+A2+ANA_{1} + A_{2} + \cdots A_{N}으로 갈 수 있겠는가? 있다면 순열을 construct하고, 아니면 -1.


제한 :

1M<N1051 \le M < N \le 10^{5}, 1Ai1091 \le A_{i} \le 10^{9}, 이불의 위치 Xj1014X_{j} \le 10^{14}.

AiA_{i}는 서로 다른 수이다.


This section is intentionally left blank.






















Induction - O(N2)O(N^2) solution


이 문제의 원본은 IMO 2009 p6(스포주의, 기만주의)이다.

결론부터 말하자면, A1+A2++ANA_{1} + A_{2} + \cdots + A_{N}에 이불이 있는 경우만 아니라면 항상 이불을 밟지 않고 갈 수 있다.


Claim. A1++ANA_{1} + \cdots + A_{N}에 이불이 존재하지 않고 M<NM < N이라면 항상 이불을 밟지 않고 가는 방법이 존재한다.


M<NM < N이 성립한다는 데 주목하자. N=0,1N = 0,1이라면 이불이 존재하지 않기 때문에 항상 가능하다.

N=1N = 1 이야기를 꺼내는 거 보니 뭔가 induction을 잘 때리면 될 것 같다는 생각이 든다. 이 문제에서 핵심적인 아이디어 2개는 가장 긴 점프(신발) ANA_{N}과 가장 가까이 있는 이불이다.


매끄러운 설명을 위해, 다음의 정의 및 가정을 도입한다 :

- A1<A2<<ANA_{1} < A_{2} < \cdots < A_{N}

- "AiA_{i}에 이불이 있다" = "현재 위치에서 AiA_{i}만큼 점프했을 때 이불이 있다."


핵심 아이디어 2개를 가지고 Case study를 하자.


Case 0. 남은 이불이 0개인 경우 (base case)


남은 점프를 모두 사용하면 된다. 끝. 이 경우는 N=0,1N = 0,1을 포함하므로, 나머지 경우에서는 N2N \ge 2를 가정한다.


Case 1. ANA_{N}에 이불이 있는 경우


Case 1-1. ANA_{N}보다 가까이 있는 이불이 없는 경우


ANA_{N}만큼 점프한 뒤에 신발 N1N-1개, 이불 M1M-1개짜리 부분문제를 푼다. 부분문제에서도 이불<신발임에 주목하자.

그 뒤에 ANA_{N}과 부분문제에서의 첫번째 점프를 swap하면 ANA_{N}에 있는 이불과 그 뒤의 이불을 모두 밟지 않을 수 있다.


Case 1-2. ANA_{N}보다 가까운 이불이 있는 경우


N1N-1개의 pair (Ai,Ai+AN)(A_{i}, A_{i} + A_{N})를 고려하자. ANA_{N}을 제외한 이불의 개수는 M1<N1M-1 < N-1이므로, 비둘기집의 원리에 의해 어떤 jj가 존재해서, Aj,Aj+ANA_{j},A_{j} + A_{N} 모두 이불이 없다.


따라서 Aj,ANA_{j},A_{N}으로 2번 점프하고, 신발 N2N-2개와 이불 MkM-k개짜리 부분문제를 풀면된다.

여기서 k :=Aj+ANk := A_{j} + A_{N}보다 가까운 이불의 개수. (가장 가까운 이불), (ANA_{N}의 이불)이 모두 스킵되므로 k2k \ge 2이다.


Case 2. ANA_{N}에 이불이 없는 경우


Case 2-1. ANA_{N}보다 가까운 이불이 없는 경우


일단 ANA_{N}으로 점프를 하고, 가장 가까운 이불을 고려하지 않은 신발 N1N-1개, 이불 M1M-1개짜리 부분문제를 푼다.

만약 부분문제의 tt번째 점프가 가장 가까운 이불에 착지한다고 하면, t+1t+1번째 점프와 ANA_{N}을 swap하면 된다. (Remark - tt번째 점프는 마지막 점프가 아님이 보장된다)


Case 2-2. ANA_{N}보다 가까운 이불이 있는 경우


그냥 ANA_{N}으로 점프하면 이불의 개수는 하나 이상 줄어든다. 끝.

이를 Naive하게 구현하면 O(N2)O(N^2) 내지는 O(N2lgN)O(N^2\lg N)에 문제를 해결할 수 있다.



Optimization - O(NlgN)O(NlgN) solution


케이스별로 시간복잡도를 적어보자.


현재 상황이 어떤 Case에 속하는지는 신발, 이불을 관리하는 set 등을 사용해 O(lgN)O(\lg N)에 알 수 있다.


Case 1-1 : O(1)O(1)

비고 : swap이 필요하다.


Case 1-2 : 모든 AiA_{i}를 돌면서 Aj,Aj+ANA_{j}, A_{j}+A_{N}이 모두 이불의 set에 속하지 않는 jj를 찾으면 break해주면 된다. O(klgN)O(k\lg N).

비고 : Case 1-2의 모든 kk를 합한 값은 MM이하이므로, Case 1-2의 모든 호출을 다 합한 복잡도는 amortized O(NlgN)O(N\lg N)이다.


Case 2-1 : 부분문제에 대해 이동거리의 누적합을 저장하는 배열이 필요해 보인다. 누적합 O(N)O(N). swap 때문에 계속 갱신이 일어나서 O(1)O(1)이 안된다.


Case 2-2 : O(1)O(1).


결국 Case 2-1의 갱신(swap)을 잘 버틸 수 있는 자료구조가 있으면 되는데, 이는 BIT를 사용하면 충분하다.

즉, BIT에 ii번째 점프를 BIT의 ii번째 entry에 저장하되, BIT.sum(j)=xBIT.sum(j) = xjj를 (존재한다면) report하는 쿼리 함수를 적절히 O(lgN)O(\lg N)으로 구현하면 모든 Case가 O(lgN)O(\lg N)이 되어서 문제를 해결할 수 있다. 그냥 BIT 이진 탐색으로 O(lg2N)O(\lg^{2} N)짜리 쿼리를 짜도 되고, 이 방법도 시간 내에 돌아간다고 한다.


Details


- iiAiA_{i}가 일대일대응되기 때문에, 인덱스를 일일이 들고 다니지 않고 마지막에 출력할 때만 map으로 찍으면 된다.

- BIT에서 query(x)를 구현하는 코드는 다음과 같다. f[i]는 BIT의 i번째 entry를 나타낸다.

int bs(ll q){
int ans = 0;
ll csum = 0;
for(int b = 16; b >= 0; b--){
if(csum + f[ans | 1 << b] == q) return ans | 1 << b;
else if(csum + f[ans | 1 << b] < q) ans |= 1 << b, csum += f[ans | 1 << b];
}
return 0;
}
- set::rbegin() (또는 --set::end()), set::begin() 등의 iterator가 도움이 된다.