Notice
Recent Posts
Recent Comments
Link
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Tags
more
Archives
Today
Total
관리 메뉴

Eau de Cologne

SCPC 2024 2차 예선 풀이 본문

rkgk

SCPC 2024 2차 예선 풀이

cologne 2024. 7. 27. 21:26

문제와 풀이 간략하게만 작성합니다.

1. 연속 1

$0$과 $1$로 구성된 수열이 주어집니다. 수열에 있는 $0$을 $1$로 바꾸는 데에는 한 번에 비용 $S$가 들고, $1$을 $0$으로 바꾸는 데에는 한번에 비용 $E$가 듭니다. 수열의 값을 바꿔서 $1$이 연속으로 나오는 부분이 $1$개 이하가 되도록 하고 싶습니다. 이 때의 최소 비용을 계산하세요.

풀이

수열을 앞에서부터 보고 다음과 같이 정의합니다.

  • 앞 $i$개의 원소에 대해 수열을 모두 $0$으로 만드는 비용: $X_i$
  • 수열을 $0...01...1$의 꼴로 만드는 비용: $Y_i$
  • 수열을 $0...01...10...0$의 꼴로 만드는 비용: $Z_i$

이 때 $X_{i+1} = X_i + V_i \times E; Y_{i+1} = \min(X_{i+1}, Y_i + S \times (1-V_i)); Z_{i+1} = \min(Y_{i+1}, Z_i + E \times V_i$ 와 같이 점화식을 쓸 수 있습니다.

2. 라운드 로빈

$1, \cdots, N$의 수로 이루어진 수열을 만듭니다. $i$는 $W_i$개의 수를 쓰려고 합니다. $i = 1, \cdots, N$ 순으로 반복하면서 $W_i > 0$ 이면 수열에 $i$를 추가하고 $W_i$에다가 $1$을 뺍니다. 이렇게 수열을 만들 때 $X$번째 수를 구하세요 ($X$는 쿼리로 주어집니다)

$W_1 = 2, W_2 = 1, W_3 = 3$이면 $1, 2, 3; 1, 3; 3$과 같이 수열이 쓰입니다.

풀이

수열을 $N$개의 수가 반복되는 구간, $N-1$개의 수가 반복되는 구간, ..., $1$개의 수가 반복되는 구간으로 $N$개의 구간으로 만들 수 있습니다.

각 구간은 $W$를 크기 순으로 정렬한 이후 먼저 소모되는 것을 구간에서 빼주면서 개수를 세어주면 됩니다.

$i$개의 수가 반복되는 구간이 $S$에서 시작하고 $S+ki$에서 끝나면 $S \le T < S+ki$ 에 대해 남아있는 수 중에서 $(1 + T \bmod i)$번째 수가 답입니다. 남아있는 수는 $W_i$순으로 정렬한 후 OS-rank 등을 이용해서 문제를 풀면 됩니다.

3. 방범 시스템

사건이 일어날 가능성이 있는 $n$개의 위치 $a_1, a_2, \cdots, a_n$과 각각의 위치에서 사건이 일어날 확률이 $p_1, p_2, \cdots, p_n$입니다. 여기서 $\sum_{i=1}^n p_i = 1$입니다.

각 경찰관의 위치 $q$가 주어지면, $q$에서 일어날 사건과의 거리의 기댓값을 출력합니다. 두 점 $(x_1, y_1)$과 $(x_2, y_2)$의 거리는 $\lvert x_1 - x_2 \rvert + \lvert y_1 - y_2 \rvert$입니다.

풀이

$x$와 $y$에 대해 독립적으로 풀면 됩니다. 각 $1$차원 좌표에 대해서 푸는 방법은 $n$개의 위치와 $q$개의 경찰관의 위치를 모두 정렬한 이후 스위핑을 한 번 돌려주면 됩니다.

4. 수열 탈집중화

$N$개의 수로 이루어진 수열 $A_1, A_2, \cdots, A_N$이 있습니다. 한 번의 연산으로 연속된 구간을 잡고 해당 구간의 원소를 모두 해당 구간의 최솟값으로 바꾸거나 최댓값으로 바꿀 수 있습니다.

$\sum_{1 \le i, j \le n} (A_i-A_j)^2$ 을 최대로 하기 위한 연산 수열을 출력하세요. 만약 해당하는 연산 수열이 여러개면 가장 길이가 짧은 연산 수열을 출력하세요.

풀이

관찰을 열심히 하면, 해당 수열의 모든 원소를 $A_i$들의 최댓값인 $mx$, $A_i$들의 최솟값인 $mn$만 남기고 $mx$와 $mn$의 개수의 차이를 $1$이하로 만드는 것이 필요함을 알 수 있습니다.

수열이 $..., mn, ..., mx, ...$ 과 같이 존재할 경우 이 수열의 오른쪽 부분에 연산을 해서 $..., mn, mx, mx, ..., mx$와 같이 바꾸고 왼쪽 부분에 연산을 해서 $mn, ..., mn, mx, ..., mx$와 같이 만들어서 해당 연산을 만들 수 있으므로, 연산을 한 번만 하는 것이 가능한가 확인해 보면 됩니다.

$mn$과 $mx$이외의 수가 존재하지 않으면 한 번에 가능하고 (왼쪽부터 $mn$과 $mx$중 큰 쪽의 수를 없애는 방향으로 합니다.) 이외의 수가 존재하면 $mn$과 $mx$이외의 수를 포함하지 않는 구간을 잡고 구간을 양쪽으로 늘려가면서 조건을 만족하도록 할 수 있는지 체크하면 됩니다.

코드가 더러운데 수정해보겠습니다...

5. 보물찾기

모든 정점의 차수가 $1$ 이상인 양방향 단순 그래프가 주어집니다. 각 정점은 Alice 혹은 Bob의 소유이고 정점 중 일부에는 보물이 존재합니다.

그래프의 한 정점에 말을 놓고 게임을 하는데, 각 턴이 시작할 때 해당 말이 올린 정점을 소유한 사람이 이웃한 간선 하나를 선택해 반대쪽 끝점으로 이동시킵니다. 이 때 보물이 있는 정점에 방문하면 보물을 하나 얻습니다.

$10^{100}$턴간 게임을 진행했을 때 $10^{90}$턴 이상에서 보물을 얻는다면 Alice의 승리, 아니면 Bob의 승리입니다.

모든 정점 각각에 대해 각 정점에 말을 놓고 게임을 시작할 때 누가 승리하는지 출력하세요.

풀이

  • $A$만으로 연결된 크기 $2$ 이상의 컴포넌트에 보물이 있으면 해당 컴포넌트에서 시작하면 $A$가 이깁니다.
  • $A$만으로 연결된 크기 $1$의 컴포넌트에 보물이 있으면 이웃한 정점 중 $A$가 승리하는 정점이 있으면 $A$가 이깁니다.
  • $B$만으로 연결된 컴포넌트에 양쪽에 보물이 없는 간선이 존재하면 해당 컴포넌트에서 시작하면 $B$가 이깁니다.

이제 $A$만으로 연결된 보물이 없는 정점과, $B$만으로 연결된 양쪽에 보물이 없는 간선이 존재하지 않는 컴포넌트 두 개가 존재합니다. 이 나머지 컴포넌트에 대해서는 다음 전략을 취합니다.

  • $A$는 $B$의 정점 중 보물이 있는 정점으로만 보내려고 합니다. (아니면, $B$가 단순히 이전 간선으로 돌려보내면 됩니다.)
  • $B$는 보물이 없는 정점에서 시작해서, $A$의 보물이 없는 정점으로 보내려고 합니다. (아니면, $A$와 $B$가 단순히 이전 간선으로 돌려보내면 됩니다.)

이 나머지에 대해서

  • $A$에서 나오는 정점이 존재하지 않거나, 모든 정점에서 $A$가 지면 $A$가 집니다.
  • $B$에서 나오는 정점 중 $A$가 지는 정점이 존재하면 $A$가 집니다.

나머지는 결정되지 않은 정점들입니다. 이 경우 항상 $A$가 $B$의 정점 중 보물이 있는 정점으로 보낼 수 있음로, 무한번 보물을 모을 수 있고, $A$가 이기게 됩니다.

코드

1번

#include <bits/stdc++.h>
using namespace std;

long long solve(vector<int> V, int S, int E)
{
    int N = V.size();
    vector<long long> X(N + 1, LLONG_MAX / 2), Y(N + 1, LLONG_MAX / 2), Z(N + 1, LLONG_MAX / 2);
    X[0] = Y[0] = Z[0] = 0;
    for (int i = 0; i < N; i++)
    {
        X[i + 1] = X[i] + (V[i] == 0 ? 0 : E);
        Y[i + 1] = min(X[i + 1], Y[i] + (V[i] == 1 ? 0 : S));
        Z[i + 1] = min(Y[i + 1], Z[i] + (V[i] == 0 ? 0 : E));
    }
    return Z[N];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;
    cin >> T;
    for (int t = 1; t <= T; ++t)
    {
        cout << "Case #" << t << '\n';
        int N, S, E;
        cin >> N >> S >> E;
        vector<int> V(N);
        for (int &v : V)
            cin >> v;
        cout << solve(V, S, E) << endl;
    }
}

2번

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>

using namespace std;
using namespace __gnu_pbds;
using oset = tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update>;

long long solve(vector<long long> W, vector<long long> Q)
{
    sort(Q.begin(), Q.end());
    int N = W.size();
    vector<int> V(N);
    iota(V.begin(), V.end(), 0);
    sort(V.begin(), V.end(), [&](int a, int b)
         { return W[a] < W[b]; });

    oset S;
    for (int i = 0; i < N; i++)
        S.insert(i);

    long long T = 0;
    long long until = 0;
    long long tp = 0;
    long long ans = 0;
    for (auto x : V)
    {
        long long ts = 1LL * (W[x] - T) * S.size();
        while (tp < (int)Q.size() && Q[tp] < until + ts)
        {
            int ret = *S.find_by_order((Q[tp] - until) % S.size());
            ans += ret;
            ++tp;
        }
        T = W[x];
        until += ts;
        S.erase(x);
    }
    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;
    cin >> T;
    for (int t = 1; t <= T; ++t)
    {
        cout << "Case #" << t << '\n';
        int N, Q;
        cin >> N >> Q;
        vector<long long> W(N);
        for (long long &w : W)
            cin >> w;
        vector<long long> Qry(Q);
        for (long long &q : Qry)
            cin >> q, --q;
        auto ret = solve(W, Qry) + Q;

        cout << ret << endl;
    }
}

3번

#include <bits/stdc++.h>
using namespace std;

vector<double> rsolve(vector<int> X, vector<double> P, vector<int> pos)
{
    int N = X.size(), M = pos.size();
    vector<double> ans(M);
    vector<pair<bool, int>> V;
    for (int i = 0; i < N; i++)
        V.emplace_back(false, i);
    for (int j = 0; j < M; j++)
        V.emplace_back(true, j);
    auto gK = [&](pair<bool, int> A)
    {
        if (!A.first)
            return X[A.second];
        return pos[A.second];
    };
    sort(V.begin(), V.end(), [&](auto A, auto B)
         { return gK(A) < gK(B); });

    double accs = 0, accp = 0;
    for (auto [tp, id] : V)
    {
        if (tp)
        {
            ans[id] += accp * pos[id] - accs;
        }
        else
        {
            accs += P[id] * X[id];
            accp += P[id];
        }
    }
    accs = accp = 0;
    for (auto [tp, id] : V | views::reverse)
    {
        if (tp)
        {
            ans[id] += accs - accp * pos[id];
        }
        else
        {
            accs += P[id] * X[id];
            accp += P[id];
        }
    }
    return ans;
}

vector<double> solve(vector<pair<int, int>> XY, vector<double> P, vector<pair<int, int>> ZW)
{
    int N = XY.size(), M = ZW.size();
    vector<int> X, Y, Z, W;
    for (auto &[x, y] : XY)
        X.push_back(x), Y.push_back(y);
    for (auto &[z, w] : ZW)
        Z.push_back(z), W.push_back(w);
    auto r1 = rsolve(X, P, Z);
    auto r2 = rsolve(Y, P, W);
    vector<double> V(M);
    for (int i = 0; i < M; i++)
        V[i] = r1[i] + r2[i];
    return V;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;
    cin >> T;
    for (int t = 1; t <= T; ++t)
    {
        cout << "Case #" << t << '\n';
        int N;
        cin >> N;
        vector<pair<int, int>> XY(N);
        for (auto &[x, y] : XY)
            cin >> x >> y;
        vector<double> P(N);
        for (auto &p : P)
            cin >> p;
        int M;
        cin >> M;
        vector<pair<int, int>> ZW(M);
        for (auto &[z, w] : ZW)
            cin >> z >> w;
        auto ret = solve(XY, P, ZW);
        for (auto x : ret)
            cout << fixed << setprecision(7) << x << '\n';
        cout << flush;
    }
}

4번

#include <bits/stdc++.h>
using namespace std;

const int MIN_OP = 1, MAX_OP = 2;

vector<tuple<int, int, int>> rsolve(vector<int> V)
{
    int N = V.size();
    int c1 = ranges::count(V, 1), cm1 = ranges::count(V, -1), c0 = ranges::count(V, 0);

    if (c0 == 0 && abs(c1 - cm1) <= 1)
        return {};

    if (c0 == 0)
    {
        int targ, ch;
        if (c1 > cm1)
            targ = 1, ch = (c1 - cm1) / 2;
        else
            targ = -1, ch = (cm1 - c1) / 2;

        // targ -> non_targ
        int op = targ == 1 ? MIN_OP : MAX_OP;

        int idx_non_targ = ranges::find(V, -targ) - V.begin();
        int l = idx_non_targ, r = idx_non_targ;
        int targ_cnt = 0;
        while (l > 0 && targ_cnt != ch)
        {
            if (V[--l] == targ)
                ++targ_cnt;
        }
        while (r < N - 1 && targ_cnt != ch)
        {
            if (V[++r] == targ)
                ++targ_cnt;
        }
        return {{op, l, r}};
    }
    if (c0 == 1 && c1 == cm1)
    {
        int idx_0 = ranges::find(V, 0) - V.begin();
        int l, r, op;
        if (idx_0 == 0)
            l = 0, r = 1, op = V[1] == 1 ? MAX_OP : MIN_OP;
        else
            l = idx_0 - 1, r = idx_0, op = V[idx_0 - 1] == 1 ? MAX_OP : MIN_OP;
        return {{op, l, r}};
    }
    {
        int l = N - 1, r = 0;
        for (int i = 0; i < N; i++)
            if (V[i] == 0)
                l = min(l, i), r = max(r, i);
        int targ = c1 > cm1 ? 1 : -1;
        int op = targ == 1 ? MIN_OP : MAX_OP;
        // targ -> non_targ

        // set rng_targ
        const int fr = max(c1, cm1) - (N + 1) / 2, to = max(c1, cm1) - N / 2;
        int targ_cnt = 0;
        for (int i = l; i <= r; i++)
            if (V[i] == targ)
                ++targ_cnt;
        const int cl = l, cr = r, ctarg_cnt = targ_cnt;

        for (int targ_mat : vector<int>({fr, to}))
        {
            for (bool ord : vector<bool>({true, false}))
            {
                int l = cl, r = cr, targ_cnt = ctarg_cnt;
                if (ord)
                    while (r < N - 1 && (targ_cnt != targ_mat || V[r + 1] != targ))
                    {
                        if (V[++r] == targ)
                            ++targ_cnt;
                    }
                while (l > 0 && (targ_cnt != targ_mat || V[l - 1] != targ))
                {
                    if (V[--l] == targ)
                        targ_cnt++;
                }
                if (!ord)
                    while (r < N - 1 && (targ_cnt != targ_mat || V[r + 1] != targ))
                    {
                        if (V[++r] == targ)
                            ++targ_cnt;
                    }
                if (targ_cnt == targ_mat)
                {
                    bool e_non_targ = false;
                    for (int i = l; i <= r; i++)
                        if (V[i] == -targ)
                            e_non_targ = true;
                    if (e_non_targ)
                    {
                        return {{op, l, r}};
                    }
                }
            }
        }
    }
    int mn_pos = ranges::find(V, -1) - V.begin(), mx_pos = ranges::find(V, 1) - V.begin();

    // [0, ..., c] [c+1, ..., N-1]
    int c = N / 2 - 1;
    if (mn_pos < mx_pos)
    {
        if (mn_pos <= c)
            return {{MAX_OP, mn_pos + 1, N - 1}, {MIN_OP, 0, c}};
        else
            return {{MIN_OP, 0, mn_pos}, {MAX_OP, c + 1, N - 1}};
    }
    else
    {
        if (mx_pos <= c)
            return {{MIN_OP, mx_pos + 1, N - 1}, {MAX_OP, 0, c}};
        else
            return {{MAX_OP, 0, mx_pos}, {MIN_OP, c + 1, N - 1}};
    }
}

bool verify(vector<int> V, vector<tuple<int, int, int>> R)
{
    for (auto [op, l, r] : R)
    {
        if (op == MIN_OP)
        {
            int mn = *min_element(V.begin() + l, V.begin() + r + 1);
            for (int i = l; i <= r; i++)
                V[i] = mn;
        }
        else
        {
            int mx = *max_element(V.begin() + l, V.begin() + r + 1);
            for (int i = l; i <= r; i++)
                V[i] = mx;
        }
    }
    return ranges::count(V, 0) == 0 && abs((int)ranges::count(V, 1) - (int)ranges::count(V, -1)) <= 1;
}


vector<tuple<int, int, int>> solve(vector<int> V)
{
    int mx = ranges::max(V);
    int mn = ranges::min(V);
    if (mx == mn)
        return {};
    vector<int> U;
    for (int v : V)
    {
        if (v == mx)
            U.push_back(+1);
        else if (v == mn)
            U.push_back(-1);
        else
            U.push_back(0);
    }
    return rsolve(U);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;
    cin >> T;
    for (int t = 1; t <= T; ++t)
    {
        cout << "Case #" << t << '\n';
        int N;
        cin >> N;
        vector<int> V(N);
        for (int &v : V)
            cin >> v;
        auto ret = solve(V);
        cout << ret.size() << endl;
        for (auto [tp, l, r] : ret)
            cout << tp << " " << l + 1 << " " << r + 1 << '\n';
        cout << flush;
    }
}

5번

#include <bits/stdc++.h>
using namespace std;

enum STATE
{
    WIN,
    LOSE,
    UNK,
};

struct UFD
{
    vector<int> p;
    UFD(int n) : p(n) { iota(p.begin(), p.end(), 0); }
    int Find(int a) { return a == p[a] ? a : p[a] = Find(p[a]); }
    void Union(int a, int b) { p[Find(a)] = Find(b); }
};

string solve(string AB, string T, vector<pair<int, int>> E)
{
    int N = AB.size();
    UFD ufd(N);
    vector<bool> non_star_edge(N);
    for (auto [u, v] : E)
        if (AB[u] == AB[v])
        {
            ufd.Union(u, v);
            if (T[u] == '.' && T[v] == '.' && AB[u] == 'B')
                non_star_edge[u] = non_star_edge[v] = true;
        }
    vector<int> sz(N), star(N), non_star(N);
    for (int i = 0; i < N; i++)
    {
        sz[ufd.Find(i)] += 1;
        if (T[i] == 'T')
            star[ufd.Find(i)] += 1;
        if (non_star_edge[i])
            non_star[ufd.Find(i)] += 1;
    }
    vector<STATE> ans(N, UNK);
    vector<int> As, Bs;
    for (int i = 0; i < N; i++)
        if (ufd.Find(i) == i)
        {
            if (AB[i] == 'A')
            {
                if (sz[i] >= 2 && star[i] >= 1)
                    ans[i] = WIN;
                // sz[i] == 1 && star[i] == 1: calculate after
                if (sz[i] >= 1 && star[i] == 0)
                {
                    As.push_back(i);
                    ans[i] = UNK; // (only outgoing to star)
                }
            }
            else
            {
                if (non_star[i])
                    ans[i] = LOSE;
                else
                    ans[i] = UNK, Bs.push_back(i); // outgoing only accounts for (non_star B -> non_star component A)
            }
        }
    vector<vector<int>> G(N), iG(N);
    vector<int> req_lose(N);
    for (auto b : Bs)
        req_lose[b] = 1;
    for (auto [u, v] : E)
        for (int i = 0; i < 2; ++i, swap(u, v))
        {
            int ux = ufd.Find(u), vx = ufd.Find(v);
            if (ux == vx)
                continue;
            assert(AB[ux] != AB[vx]);
            if (AB[u] == 'A' && star[ux] == 0 && T[v] == 'T' && !non_star[vx])
                G[ux].push_back(vx);
            if (AB[u] == 'B' && !non_star[u] && T[u] != 'T' && star[vx] == 0)
                G[ux].push_back(vx);
        }
    for (int i = 0; i < N; i++)
    {
        sort(G[i].begin(), G[i].end());
        G[i].erase(unique(G[i].begin(), G[i].end()), G[i].end());
        for (int j : G[i])
            iG[j].push_back(i);
    }
    queue<int> Q;
    for (auto a : As)
    {
        req_lose[a] = G[a].size();
        if (req_lose[a] == 0)
            Q.emplace(a);
    }
    while (!Q.empty())
    {
        int a = Q.front();
        Q.pop();
        ans[a] = LOSE;
        for (auto b : iG[a])
            if (--req_lose[b] == 0)
                Q.push(b);
    }
    for (auto a : As)
        if (ans[a] == UNK)
            ans[a] = WIN;
    for (auto b : Bs)
        if (ans[b] == UNK)
            ans[b] = WIN;
    vector<vector<int>> rG(N);
    for (auto [u, v] : E)
        rG[u].push_back(v), rG[v].push_back(u);
    for (int i = 0; i < N; i++)
        if (AB[i] == 'A' && sz[i] == 1 && star[i] == 1)
        {
            for (auto x : rG[i])
                if (ans[ufd.Find(x)] == WIN)
                    ans[i] = WIN;
            if (ans[i] == UNK)
                ans[i] = LOSE;
        }

    string s;
    for (int i = 0; i < N; i++)
    {
        assert(ans[ufd.Find(i)] != UNK);
        if (ans[ufd.Find(i)] == WIN)
            s += 'A';
        else
            s += 'B';
    }

    return s;
}

int main()
{
    // check();
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T;
    cin >> T;
    for (int t = 1; t <= T; ++t)
    {
        cout << "Case #" << t << '\n';
        int N, M;
        cin >> N >> M;
        string S, T;
        cin >> S >> T;
        vector<pair<int, int>> E(M);
        for (auto &[a, b] : E)
            cin >> a >> b, --a, --b;
        auto ret = solve(S, T, E);
        cout << ret << endl;
    }
}

'rkgk' 카테고리의 다른 글

SCPC 2024 1차 풀이  (0) 2024.07.06
[Rust] Lifetime elision  (0) 2023.10.06
FastIO 메모  (0) 2023.05.25
[Working In Progress] Euler Tour Trick + LCA + HLD Template  (0) 2023.05.05
가성비 에라토스테네스의 체  (0) 2023.04.27