본문 바로가기

카테고리 없음

LGCPC 2024 B, C 풀이

B

Subtask 1 : $x = 1$

구슬을 흘려보냈을 때 도착하는 위치는 왼쪽으로 이동할 때 1칸만 이동할 수 있으므로, amortized $O(1)$에 업데이트할 수 있습니다.

따라서 현재 도착 위치에서 다음 도착 위치를 naive하게 구해주어도 $O(N+Q)$에 문제가 해결됩니다.

 

Subtask 2 : $N, Q \le 5000$

쿼리가 주어질 때마다 다음 배열이 어떻게 되는지 $O(N)$에 다양한 방법으로 구할 수 있습니다. 따라서 시간복잡도는 $O(NQ)$입니다.

 

Subtask 3 : 추가 제약 조건 없음

stack을 이용하여 문제를 해결합니다. 각 $A[i]$에 $i$를 더해준 후, 스택을 활용해 최댓값이 업데이트되는 부분을 저장합니다. 그 후 그 지점을 중심으로 그 지점의 원래 높이와 주변 높이가 같아질 때까지 흘려야 하는 구슬의 수, 다음 지점을 넘어가지 않고 흘릴 수 있는 최대 구슬의 수를 미리 계산해놓으면 스위핑을 통해 문제를 해결할 수 있습니다.

 

#include <iostream>
#include <stack>
#include <string>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <algorithm>
#include <cassert>

using namespace std;
typedef long long ll;

const ll INF = 1e18;

ll V[500002];
ll pf[500002], th[500002];
vector<ll> qry;

int main()
{
    ios::sync_with_stdio(false); cin.tie(NULL);
    int n, q; cin >> n >> q;
    for(int i=1;i<=n;i++) {
        cin >> V[i];
        V[i] += i-1;
    }
    
    V[n+1] = INF;
    
    ll now = 0;
    for(int i=1;i<=q;i++) {
        int t; cin >> t;
        if(t == 1) {
            ll x; cin >> x;
            now += x;
        }
        if(t == 2) {
            qry.push_back(now);
        }
    }
    
    pf[0] = 0;
    for(int i=1;i<=n;i++) pf[i] = pf[i-1] + V[i];
    
    int st = 1;
    
    vector<int> v;
    v.push_back(st);
    for(int i=2;i<=n;i++) {
        if(V[i] > V[st]) {
            st = i;
            v.push_back(i);
        }
    }
    
    vector<ll> v2, v3;
    int m = (int)v.size();
    v2.resize(m);
    v3.resize(m);
    
    v.push_back(n+1);
    
    v2[0] = 0;
    v3[0] = 0;
    
    for(int i=0;i<m;i++) {
        v2[i] = (v[i+1] - 1) * V[v[i]] - pf[v[i+1]-1];
        v3[i] = V[v[i]] * v[i] - pf[v[i]];
    }
    int k = (int)qry.size();
    vector<ll> ans(k);
    
    now = 0;
    for(int i=0;i<k;i++) {
        while(now < m - 1 && v3[now+1] <= qry[i]) now++;
        if(qry[i] >= v2[now]) ans[i] = (qry[i] - v2[now])/(v[now+1] - 1) + V[v[now]];
        else ans[i] = V[v[now]];
        
        cout << ans[i] << "\n";
    }
}

C

Subtask 1,2 : $N \le 8, 9$

각 칸을 돌면서, 위의 두 줄(노란색 칸)이 개발되었는지 여부를 2N개의 bit로 저장하는 bitmask DP를 진행합니다.

$dp[bit][H][i] := $ 노란색 칸의 개발 여부를 $bit$, 현재 칸의 위치를 $H$, 지금까지 추가로 개발한 지역의 개수를 $i$라고 할 때 도시로 확인된 구역들의 관광가치의 최대 합

가능한 비트열의 수는 $2^{2N}=4^N$개이고, 이 dp를 $N^2$ 칸과 $K$개의 상태에 대해 진행합니다.

총 시간복잡도는 $O(KN^24^N)$이 됩니다.

 

그대로 구현하면 $N \le 8$인 데이터를 맞을 수 있으며, 토글링 기법을 통한 메모리 최적화 및 효율적인 구현을 통해 $N \le 9$까지 풀 수 있습니다.

Subtask 3,4 : $N \le 10, 11$

기본적으로 비슷한 아이디어의 dp를 진행할 수 있습니다. 2가지 상태의 bit 대신 각 칸이 아래와 같은 3가지 상태를 가질 수 있습니다.

  • 0 : 개발되지 않음
  • 1 : 개발되었으나, 도시가 될 가능성이 없음
  • 2 : 개발되었으며 도시가 될 가능성이 있음

상태를 3가지로 늘릴 경우 미리 가능성을 판별할 수 있기 때문에, 위의 두 줄을 기록하는 대신 위의 한 줄을 기록하는 것으로 충분합니다.

총 시간복잡도는 $O(KN^23^N)$이며, 효율적인 구현을 통해 $N \le 11$인 데이터까지 정답을 받을 수 있습니다.

Subtask 5 : $N \le 12$

Subtask 3, 4와 같은 방식으로 각 칸의 상태를 정의합니다. 단, 상태 배열에서 0과 2가 인접할 수 없다는 사실을 이용하여 가능한 상태의 총 가짓수를 줄일 수 있습니다. $N = 12$일 때 이 상태의 총 개수는 대략 5만 개입니다. 이 값을 $S$라고 할 때, $O(KN^2S)$의 시간복잡도로 문제를 풀 수 있습니다.

0, 1, 2로 이루어졌으며 0과 2가 인접한 배열 중, 마지막 원소가 0, 1, 2인 것의 가짓수를 각각 $a_i, b_i, c_i$라고 할 때, 다음 점화식이 성립합니다.

  • $a_{k+1} = a_k + b_k$
  • $b_{k+1} = a_k + b_k + c_k$
  • $c_{k+1} = b_k + c_k$

이때

$a_{k+1} + \sqrt{2}b_{k+1} + c_{k+1} = (1+\sqrt{2})a_k + (2+\sqrt{2})b_k + (1+\sqrt{2})c_k = (1+\sqrt{2})(a_k+\sqrt{2}b_k+c_k)$

따라서 $S = O((1+\sqrt{2})^N)$이 되고, 문제의 총 시간복잡도는 $O(KN^2(1+\sqrt{2})^N)$이 됩니다.

#include <iostream>
#include <stack>
#include <string>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <algorithm>
#include <cassert>
 
using namespace std;
typedef long long ll;
 
const ll INF = 1e9;
 
int grid[12][12];
int value[12][12];
int pw3[13];
vector<int> bit[47321];
int nxt[47321][3];
int id[531441];
int dp[47321][51];
int tmp[47321][51];
 
int main()
{
    ios::sync_with_stdio(false); cin.tie(NULL);
    
    pw3[0] = 1;
    for(int i=1;i<=12;i++) {
        pw3[i] = pw3[i-1] * 3;
    }
    
    int n, k; cin >> n >> k;
    
    int cnt = 0;
    for(int i=0;i<pw3[n];i++) id[i] = -1;
    for(int i=0;i<pw3[n];i++) {
        vector<int> v(n);
        int now = i;
        bool flag = true;
        for(int j=0;j<n;j++) {
            v[j] = now%3;
            if(j > 0) {
                if(v[j-1] == 0 && v[j] == 2) {
                    flag = false;
                    break;
                }
                if(v[j-1] == 2 && v[j] == 0) {
                    flag = false;
                    break;
                }
            }
            now /= 3;
        }
        
        if(flag) {
            bit[cnt] = v;
            id[i] = cnt;
            cnt++;
        }
    }
    
    for(int i=0;i<cnt;i++) {
        nxt[i][0] = nxt[i][1] = nxt[i][2] = -1;
    }
    
    for(int i=0;i<pw3[n];i++) {
        int j = id[i];
        if(j < 0) continue;
        if(i%3 == 0) {
            nxt[j][0] = id[(i*3)%pw3[n] + 0];
            nxt[j][1] = id[(i*3)%pw3[n] + 1];
        } else if(i%3 == 1) {
            nxt[j][0] = id[(i*3)%pw3[n] + 0];
            nxt[j][1] = id[(i*3)%pw3[n] + 1];
            if(bit[j][n-1] != 0) nxt[j][2] = id[(i*3)%pw3[n] + 2];
        } else {
            nxt[j][1] = id[(i*3)%pw3[n] + 1];
            if(bit[j][n-1] != 0) nxt[j][2] = id[(i*3)%pw3[n] + 2];
        }
    }
    
    
    
    
    for(int i=0;i<n;i++) {
        for(int j=0;j<n;j++) {
            cin >> grid[i][j];
        }
    }
    
    for(int i=0;i<n;i++) {
        for(int j=0;j<n;j++) {
            cin >> value[i][j];
        }
    }
    
    for(int i=0;i<cnt;i++) {
        for(int j=0;j<=k;j++) {
            dp[i][j] = -INF;
        }
    }
    
    dp[0][0] = 0;
    
    for(int i=0;i<n;i++) {
        for(int j=0;j<n;j++) {
            
            for(int a=0;a<cnt;a++) {
                for(int b=0;b<=k;b++) {
                    tmp[a][b] = -INF;
                }
            }
            
            for(int a=0;a<cnt;a++) {
                for(int b=0;b<=k;b++) {
                    if(dp[a][b] == -INF) continue;
                    int u0 = nxt[a][0], u1 = nxt[a][1], u2 = nxt[a][2];
                    if(u0 >= 0 && grid[i][j] == 0) {
                        tmp[u0][b] = max(tmp[u0][b], dp[a][b]);
                    }
                    if(u1 >= 0) {
                        int T = dp[a][b];
                        if(bit[a][n-1] == 2) T += value[i-1][j];
                        if(grid[i][j] == 0 && b < k) {
                            tmp[u1][b+1] = max(tmp[u1][b+1], T);
                        }
                        else if(grid[i][j] == 1){
                            tmp[u1][b] = max(tmp[u1][b], T);
                        }
                    }
                    if(u2 >= 0 && j != 0 && j != n-1) {
                        int T = dp[a][b];
                        if(bit[a][n-1] == 2) T += value[i-1][j];
                        if(grid[i][j] == 0 && b < k) {
                            tmp[u2][b+1] = max(tmp[u2][b+1], T);
                        }
                        else if(grid[i][j] == 1){
                            tmp[u2][b] = max(tmp[u2][b], T);
                        }
                    }
                }
            }
            
            for(int a=0;a<cnt;a++) {
                for(int b=0;b<=k;b++) {
                    dp[a][b] = tmp[a][b];
                }
            }
        }
    }
    
    int ans = 0;
    for(int a=0;a<cnt;a++) {
        ans = max(ans, dp[a][k]);
    }
    
    cout << ans;
}