ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • BOJ 18622 Dedenne
    Problem 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

    댓글

Designed by Tistory.