BOJ 18622 Dedenne
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;
}