-
BOJ 18622 DedenneProblem Solving/Solution Sketch 2020. 5. 8. 20:22
BOJ 18622 Dedenne (GP of Warsaw, Petrozavodsk Programming Camp Summer 2019 Day 5 #D)
3일 정도에 걸쳐 5시간정도 고민했고, 찾아낸 풀이가 매우 인상적인 문제였다.
우와 정말 데덴네~문제 설명
쿼리 $T$개가 주어진다. ($1 \leq T \leq 50000$)
각 쿼리마다 정수 $N$이 하나씩 주어지는데, 문제에서 주어진 Cost가 최소가 되도록 조건에 맞게 $N$개의 Binary String을 선택해야 한다. ($2 \leq N \leq {10}^{15}$)
조건은
1. 선택한 모든 Binary String에서 연속한 두 자리에 모두 0이 올 수 없다.
2. 선택한 한 Binary String이 다른 Binary String의 Prefix가 될 수 없다.
Cost는 $f(x)=\sum_{j=1}^{x} {[1+{log}_{2}{j}]}$이라 할 때, 모든 Binary String에 대하여 이 문자열이 자신이 선택한 Binary String들의 Prefix로 오는 횟수를 $k$회라 하면 $f(k)$들의 총합이다.
풀이
1. $O({N}^{2})$
입력된 $n$에 대한 최소 Cost를 $g(x)$이라 하면, $g(x)=f(x)+\min_{1\leq i<n}(g(i)+f(i)+g(x-i))$라는 점화식을 간단하게 도출할 수 있다.
이를 Naive하게 계산해 준다면 $O({N}^{2})$의 시간에 $g(x)$를 모두 구할 수 있다.
2. $O(NlogN)$
함수 $g(x)$가 Convex함을 보이자.우선 $f(x)$가 Convex함은 자명하다.$\forall x\leq n, x\in \mathbb N$에 대하여 $g(x)$가 Convex하다고 가정하자.$x\leq n$일 때 고정된 i에 대하여 $(g(i)+f(i)+g(x-i))$는 Convex하다.$\min_{1\leq i<n}$$(g(i)+f(i)+g(x-i))$는 Convex한 함수들의 최솟값이므로 마찬가지로 x에 대하여 Convex하다.따라서, $\forall x\leq n+1, x\in \mathbb N$에 대하여서도 $g(x)$는 Convex하다.수학적 귀납법에 의하여 $\forall x\in \mathbb N$에 대하여 $g(x)$는 Convex하다.증명에 오류가 있습니다. 추후 수정하도록 하겠습니다.
$g(x)$가 Convex함을 보였기 때문에, $1\leq n \leq x$인 모든 $n$에 대한 $g(x), f(x)$값을 가지고 있다면 삼진 탐색을 통해서 $O(logN)$의 시간에 $g(x+1)$을 구할 수 있음을 알 수 있다.
따라서 총 $O(NlogN)$의 시간복잡도로 $g(x)$를 모두 구할 수 있다.
3. $O(M{log}^{3}N+TlogM)$
$n$의 범위가 $10^{15}$이므로 $O(NlogN)$에서 시간을 더 줄여야 한다.
"기울기가 변하는 횟수가 충분히 적다면 기울기가 변하는 점이랑 그 때의 함숫값, 기울기를 미리 다 구해놓을 수 있지 않을까?"라는 발상을 해보자.
(...)이를 실제로 구해보는 것은 어렵지 않다. 배열에 위에서 언급한 값들의 초기값을 저장해두고, 이분 탐색을 통해 마지막으로 추가한 점 다음의 기울기가 변하는 점을 구해서 $10^{15}$ 이전까지 계속 추가해 나가면 된다.
다음으로 기울기가 변하는 점을 찾는데는 이분 탐색에 $logN$, 삼분 탐색에 $logN$, $f(x)$값을 구하는 데 $logN$이 걸리므로 총 $O({log}^{3}N)$의 시간이 걸린다.
구현하여 실행해 보면, 기울기가 변하는 점의 수는 $10^{15}$ 이전에 총 1833개가 나타난다. (위의 시간 복잡도의 $M$은 1833이다.)
이제 쿼리로 주어지는 각 $t$에 대해서는 $t$ 이전에 마지막으로 기울기가 변하는 점을 std::lower_bound 등을 이용해 구하여 쉽게 계산할 수 있다.
Sample Code
아래의 코드는 BOJ 기준 940ms 정도에 작동하였다.
#include <bits/stdc++.h> #define eb emplace_back using namespace std; using ll = long long; const ll MX = 1e15; ll f(ll x) { ll r=0; for (int i=0; ; i++) { if ((1ll<<i+1)>x) return r+(x-(1ll<<i)+1)*(i+1); r+=(1ll<<i)*(i+1); } } vector<ll> P, S, T; ll g1(ll n) { int b=upper_bound(P.begin(), P.end(), n)-P.begin()-1; return T[b]+S[b]*(n-P[b]); } ll g2(ll n) { ll s=1, e=n-1; while (s+3<e) { ll x1=(s*2+e)/3, x2=(s+e*2)/3; ll v1=g1(x1)+g1(n-x1)+f(x1), v2=g1(x2)+g1(n-x2)+f(x2); if (v1>v2) s=x1; else e=x2; } ll mn=g1(n-1)+1; for (ll i=s; i<=e; i++) mn=min(mn, g1(i)+g1(n-i)+f(i)); return mn+f(n); } int main() { cin.tie(0)->sync_with_stdio(0); P.eb(1); S.eb(4); T.eb(1); while (1) { ll s=P.back()+1, e=1e16; while (s<e) { ll m=(s+e+1)/2; ll x=g1(m), y=g2(m); if (x!=y) e=m-1; else s=m; } if (s>MX) break; T.eb(T.back()+(s-P.back())*S.back()); S.eb(g2(s+1)-g1(s)); P.eb(s); } int T; ll N; cin>>T; while (T--) cin>>N, cout<<g1(N)<<'\n'; return 0; }
'Problem Solving > Solution Sketch' 카테고리의 다른 글
BOJ 19558 Joker (0) 2023.01.23 BOJ 19935 Carnival Tickets (0) 2021.02.16 BOJ 19934 Connecting Supertrees (0) 2021.02.16 JOISC 2020 연습 (8/3~8/15) (0) 2020.08.16 BOJ 18254 쿼리와 쿼리 (0) 2020.03.11