티스토리 뷰

인터랙티브 문제와 투스텝 문제 몇 개를 풀어보았다.

BOJ 31975. Staring Contest

만점을 받기 위해서는 $q \le n + 25$회 이내에 문제를 해결해야 한다. 데이터가 사전에 결정되어 있고 제한이 애매하므로 무작위화 알고리즘을 고려해볼 수 있다.

 

어떤 사람 $s$를 정하고, 이 사람과 다른 사람들에 대한 쿼리를 계속 날린다. 날리다가 이미 들어온 값 $v$가 또 들어오는 시점이 있을 것이다. 두 사람의 실력이 다르므로 이 값은 사람 $s$의 실력이라고 생각할 수 있을 것이다. 즉, 사람 $s$의 실력을 $v$로 고정할 수 있다. 또한 앞에서 쿼리를 날렸을 때 $v$보다 작은 값이 들어온 경우 그 값들을 해당 사람들의 실력으로 확정할 수 있을 것이다. 이제 $v$라는 결과가 나온 두 사람 중 하나를 $s$로 두고, 아직 답을 모르는 사람들에 대해 이 과정을 계속 반복하면 된다.

 

사람의 순서를 랜덤으로 섞어 무작위화 알고리즘을 적용한다. 실제로 쿼리 수를 추정하려면 다음 문제에 대한 해답을 구하면 된다.

  • 길이 $n$의 순열 $A$를 무작위로 섞고, prefix max 배열 $P$를 만들었을 때, $P$에 있는 서로 다른 원소 개수의 기댓값이 얼마인가?

위 문제에 대한 답은 대략적으로 $O(\log n)$ 근처라고 추정할 수 있다. 어떤 배열의 최댓값의 위치는, 그 배열의 길이가 $l$이라고 할 때 평균적으로 $\frac{l}{2}$ 정도에 위치한다. 이 최댓값 뒤에 있는 수들은 답에 영향을 주지 않으므로, 최댓값 앞의 수만 본 뒤 같은 작업을 반복한다. 한 번 작업을 할 때마다 배열의 절반 정도가 줄어듬을 알 수 있으므로, 실제로 최댓값이 갱신되는 일은 평균적으로 $O(\log n)$번 정도일 것이라고 생각할 수 있다. 따라서 쿼리 횟수가 $n + \log n$ 정도라고 근사할 수 있다.

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int n;
vector<int> indices;
int ans[1502];

int main(){
    scanf("%d", &n);

    int s = 1;
    vector<int> vec;
    for(int i=2; i<=n; i++) vec.push_back(i);
    random_shuffle(vec.begin(), vec.end());

    while(true){
        map<int, int> mp;
        bool duplicate = 0;
        while(!vec.empty()){
            int x = vec.back(); vec.pop_back();
            printf("? %d %d\n", s, x);
            fflush(stdout);
            int v;
            scanf("%d", &v);

            if(!mp.count(v)){
                mp[v] = x;
                continue;
            }
            duplicate = 1;
            for(auto [y, z]: mp) ans[z] = y;
            ans[s] = v;
            s = mp[v];
            vec.push_back(x);
            break;
        }
        if(duplicate) continue;
        for(auto [y, z]: mp){
            ans[z] = y;
        }
        ans[s] = mp.rbegin()->first;
        break;
    }
    printf("! ");
    for(int i=1; i<=n; i++) printf("%d ", ans[i]);
}

BOJ 10117. Question

문제

A에게는 $N \le 920$인 두 양의 정수 $x$와 $y$가 주어진다. A는 B에게 양의 정수 $h$를 보낼 수 있다.

 

B는 양의 정수 $h$와 정수 $q$를 받는다. $q$는 $q=x$ 또는 $q=y$인데, $h$를 보고 어느 쪽에 해당하는지 맞혀야 한다.

 

보내는 $h$의 범위를 최소화해 목표를 달성해 보자.

풀이

먼저 $h=20$인 경우의 풀이를 보자. $x$와 $y$는 서로 다르기 때문에, 이진수로 나타냈을 때 서로 다른 비트가 존재한다. 그 비트는 $2^0$부터 $2^9$까지의 비트 중 하나일 것이므로, 10가지 중 하나이다.

 

정수 $h$를 통해 해당 비트가 몇 번째 비트인지, 그리고 $x$에서 그 비트의 값이 무엇인지를 인코딩해 보낼 수 있다. 이때 $h$의 최댓값은 $10 \times 2 = 20$이 된다.

 

여기서 $h$의 최댓값을 $12$까지 끌어올리려면 다른 방법이 필요하다. ${}_{12} C_6=924$인 점을 이용해 보자. 길이가 $12$이고 $0$과 $1$이 각각 $6$개씩 있는 모든 문자열을 만들고, 그 중 사전순으로 $x$번째인 것과 $y$번째인 것을 보자. 이때 $x$번째 문자열에서는 $0$이지만 $y$번째 문자열에서는 $1$인 것이 반드시 존재한다. 그 위치를 $h$로 보낼 것이다. B는 받은 수 $q$의 $h$번째 자리를 보고, 그 자리가 $0$이면 yes, $1$이면 no를 출력하면 된다.

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

string s[924];
void init(){
    string A = "(((((())))))";
    int cnt = 0;
    do {
        s[cnt++] = A;
    } while(next_permutation(A.begin(), A.end()));
}

int m, n, t;
int x, y;

int main(){
    init();
    scanf("%d %d %d", &m, &n, &t);
    if(m==1){
        while(t--){
            int x, y;
            scanf("%d %d", &x, &y);
            int h = 0;
            while(s[x][h] == ')' || s[y][h] == '(') h++;
            printf("%d\n", h+1);
        }
    }
    else{
        while(t--){
            int q, h;
            scanf("%d %d", &q, &h);
            printf("%s\n", s[q][h-1] == '(' ? "yes" : "no");
        }
    }
}

BOJ 10232. Saveit

문제

  • 정점이 $N$개인 그래프가 있다. 모든 간선은 양방향이며 가중치가 $1$이다.
  • 앞 $H$개의 정점으로부터 다른 모든 정점까지의 최단거리를 모두 구할 수 있도록 짧은 길이의 비트스트링을 전달해야 한다.
  • $N \le 1000$, $H \le 36$

풀이

Subtask 2까지는 모든 최단거리를 구해 전달해도 된다. 최단거리는 $1000$ 이하이므로 $10$비트로 전달할 수 있고, $10NH \le 360 , 000$ 비트 안으로 전달할 수 있다.

 

Subtask 3부터는 효율적인 방법이 필요하다. 다음 사실을 알 수 있다.

  • 어떤 두 정점 $x$와 $y$가 인접하다면, 허브 $h$에서 $x$까지의 최단거리 $dist(h, x)$와 $y$까지의 최단거리 $dist(h, y)$는 최대 1 차이난다.

따라서, 다음과 같은 방법을 생각해볼 수 있다: 임의의 스패닝 트리 $T$를 잡고, $T$에서 각 정점들의 부모 노드를 인코딩한다. 여기에 $1000 \times 10$ 개의 비트가 필요하다. 이후 각 허브에 대해 다음을 인코딩한다.

  • $T$의 루트 노드까지의 거리 ($10$ 비트)
  • $T$의 루트 노드가 아닌 각 노드에 대해, 부모 노드의 최단거리와 자기 노드의 최단거리의 차이 ($-1$, $0$, $1$ 중 하나) ($999 \times 2$ 비트)

위와 같이 하면 허브 하나당 대략 $2000$ 비트를 이용해 인코딩할 수 있다. 이렇게 하면 약 $82000$ 비트가 필요하다. 스패닝 트리 $T$를 정점 $0$에서 시작하는 BFS 트리로 잡으면 $2000$ 비트를 아낄 수 있으므로 약 $80000$ 비트 정도가 필요할 것이다. 이걸로 Subtask 3을 맞을 수 있는지는 잘 모르겠다. 아마 몇 비트를 더 아껴야 될 수도 있다. 이 부분이 그렇게 어려워 보이진 않는다.

 

여기서 가장 쉽게 줄일 수 있는 부분은 저 $999 \times 2$ 비트를 줄이는 것이다. 이론적으로는 $\log_2 {3^{999}}$ 비트가 필요하고, 이것은 약 $1583.4 $ 정도이므로 노드당 $1584$ 비트 정도면 충분하다. 이 정도면 문제를 풀기에는 충분하지만, 큰 수를 구현해야 하므로 그다지 실용성이 있어 보이진 않는다. 가장 그럴듯한 방법은 $3^i \sim 2^j$인 $(i, j)$ 쌍을 찾아 수 $i$개의 정보를 $j$비트에 담는 것이다. 예를 들어, $3^5$는 $2^8$보다 약간 작으므로 5개의 정보를 8비트에 담을 수 있을 것이다. 이렇게 하면 각 허브당 $1000 \div 5 \times 8 = 1600$ 비트가 필요하고, 이 정도로도 만점을 받기에 충분하다.

#include "grader.h"
#include "encoder.h"
#include <bits/stdc++.h>

using namespace std;

namespace {
    int n, h, m;
    vector<int> link[1002];
    int par[1002];
    bool visited[1002];
    int dist[50][1002];
}

void encode(int nv, int nh, int ne, int *v1, int *v2){
    n = nv, h = nh, m = ne;
    for(int i=0; i<m; i++){
        link[v1[i]].push_back(v2[i]);
        link[v2[i]].push_back(v1[i]);
    }

    /// BFS 트리를 인코딩
    queue<int> que;
    que.push(0), visited[0] = 1;
    while(!que.empty()){
        int x = que.front(); que.pop();
        for(int y: link[x]){
            if(visited[y]) continue;
            visited[y] = 1, par[y] = x;
            que.push(y);
        }
    }
    for(int i=1; i<n; i++) for(int j=0; j<10; j++) encode_bit((par[i]>>j)&1);

    /// 거리 차이를 인코딩
    for(int s=0; s<h; s++){
        fill(visited, visited+n+1, 0);
        que.push(s), visited[s] = 1, dist[s][s] = 0;
        while(!que.empty()){
            int x = que.front(); que.pop();
            for(int y: link[x]){
                if(visited[y]) continue;
                visited[y] = 1, dist[s][y] = dist[s][x] + 1;
                que.push(y);
            }
        }
        /// dist[s][0] 인코딩
        for(int i=0; i<10; i++) encode_bit((dist[s][0]>>i)&1);
        /// 나머지 거리 인코딩
        vector<int> vec;
        for(int i=1; i<n; i++){
            int v = dist[s][i] - dist[s][par[i]] + 1;
            vec.push_back(v);
            if((int)vec.size() == 5){
                int val = vec[0] + vec[1]*3 + vec[2]*9 + vec[3]*27 + vec[4]*81;
                for(int d=0; d<8; d++) encode_bit((val>>d)&1);
                vec.clear();
            }
        }
        if(!vec.empty()){
            vec.resize(5);
            int val = vec[0] + vec[1]*3 + vec[2]*9 + vec[3]*27 + vec[4]*81;
            for(int d=0; d<8; d++) encode_bit((val>>d)&1);
            vec.clear();
        }
    }
}
#include "grader.h"
#include "decoder.h"
#include <bits/stdc++.h>

using namespace std;

namespace {
    int n, h;
    vector<int> link[1002];
    int par[1002];
    int offset[50][1002];
    int dist[50][1002];

    void dfs(int x){
        for(int y: link[x]){
            for(int s=0; s<h; s++) dist[s][y] = dist[s][x] + offset[s][y];
            dfs(y);
        }
    }
}

void decode(int nv, int nh){
    n = nv, h = nh;
    for(int i=1; i<n; i++){
        int v = 0;
        for(int d=0; d<10; d++) v += decode_bit() << d;
        par[i] = v;
        link[v].push_back(i);
    }
    for(int s=0; s<h; s++){
        for(int d=0; d<10; d++) dist[s][0] += decode_bit() << d;
        for(int l=1; l<n; l+=5){
            int v = 0;
            for(int d=0; d<8; d++) v += decode_bit() << d;
            for(int x=l; x<l+5 && x<n; x++){
                offset[s][x] = v % 3 - 1;
                v /= 3;
            }
        }
    }
    dfs(0);
    for(int s=0; s<h; s++){
        for(int i=0; i<n; i++){
            hops(s, i, dist[s][i]);
        }
    }
}

BOJ 23407. Board Trick

notorious coincidence를 예전에 본 적이 있고, 그 뒤로 유튜브에서도 본 적 있던 문제로 기억한다.

 

편의상 모든 칸의 인덱스를 $0$부터 $63$으로 두자. 수가 $1$인 부분의 인덱스를 모두 XOR 했을 때 $v$가 되도록 하면 된다.

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

string s;
int t, n;
int board[64];

int main(){
    cin >> s;
    if(s == "Mia"){
        scanf("%d", &t);
        while(t--){
            int v = 0;
            for(int i=0; i<64; i++){
                scanf("%d", &board[i]);
                if(board[i]) v ^= i;
            }
            scanf("%d", &n); n--;
            board[n^v] ^= 1;
            for(int i=0; i<64; i++){
                printf("%d%c", board[i], i%8==7 ? '\n' : ' ');
            }
            puts("---");
        }
    }
    else{
        scanf("%d", &t);
        while(t--){
            int v = 0;
            for(int i=0; i<64; i++){
                scanf("%d", &board[i]);
                if(board[i]) v ^= i;
            }
            printf("%d\n", v+1);
            cin >> s;
        }
    }
}

'문제풀이 > Problem Solving Diary' 카테고리의 다른 글

Problem Solving Diary #26  (0) 2024.11.15
Problem Solving Diary #25  (0) 2024.10.13
Problem Solving Diary #23  (0) 2024.06.20
Problem Solving Diary #22 - Codeforces Round 934 (Div. 1)  (0) 2024.05.30
Problem Solving Diary #21  (0) 2024.05.30
공지사항
최근에 올라온 글
Total
Today
Yesterday