スコルの知恵袋

主にプログラミング関係の気になったこと等をまとめたブログ

Codeforces Round #656 (Div. 3) 感想

問題

Dashboard - Codeforces Round #656 (Div. 3) - Codeforces

感想

5完。可もなく不可もなくで特に何も言葉が出てこない。

A - Three Pairwise Maximums

a, b, c を出力する順番は何でもいいらしいので、x, y, z を入れ替えて  x \le y \le z となるようにしてもOK。 すると、

 \displaystyle{
  \begin{align}
    z &= \max \{ x, y, z \} = \max \{ a, b, c \} \\
    y &= \max \{ x, y \} = \max \{ a, b, c \} \\
    x &= \max \{ a, b \}
  \end{align}
}

だから、 y = z でないといけないことが分かる。 y = z のとき、 a = 1,\ b = x,\ c = y = z とすれば上式を満たすからこれを出力すればいい。

よく考えると、a, b, c のとる値は x, y, z に限っても良くて、何も考えずに全探索でもいける。

提出コード

リンク: http://codeforces.com/contest/1385/submission/87105915

void solve() {
    int Q;
    cin >> Q;
    ll v[3];
    while (Q--) {
        cin >> v[0] >> v[1] >> v[2];
        sort(v, v + 3);
        if (v[1] != v[2]) {
            cout << "NO\n";
            continue;
        }
        cout << "YES\n";
        cout << 1 << " " << v[0] << " " << v[1] << "\n";
    }
}

B - Restore the Permutation by Merger

前から見ていって、まだ登場してなければ答えの vector に push_back して、すでに登場していれば無視する。

Aより簡単じゃね?

提出コード

リンク:http://codeforces.com/contest/1385/submission/87110903

void solve() {
    int Q;
    cin >> Q;
    Vl A;
    vector<bool> used;
    while (Q--) {
        ll N;
        cin >> N;
        A.resize(N * 2);
        used.assign(N + 1, false);
        for (ll i = 0; i < N * 2; i++) {
            cin >> A[i];
        }
        for (ll i = 0; i < N * 2; i++) {
            if (!used[A[i]]) {
                cout << A[i] << " ";
                used[A[i]] = true;
            }
        }
        cout << "\n";
    }
}

C - Make It Good

要素の増減が /\ ← こんな感じになっていることが good であるための必要十分条件。 / や \ でもOK。

数列 a を後ろから見ていって、この条件がいつまで満たされるかを調べればいい。

提出コード

リンク:http://codeforces.com/contest/1385/submission/87118183

void solve() {
    int Q;
    cin >> Q;
    Vl A;
    while (Q--) {
        ll N;
        cin >> N;
        A.resize(N);
        for (ll i = 0; i < N; i++) {
            cin >> A[i];
        }
        reverse(A.begin(), A.end());
        ll ans = 0;
        bool fg = false;
        for (ll i = 1; i < N; i++) {
            if (!fg) {
                if (A[i - 1] > A[i]) {
                    fg = true;
                }
            }
            else {
                if (A[i - 1] < A[i]) {
                    ans = N - i;
                    break;
                }
            }
        }
        cout << ans << "\n";
    }
}

D - a-Good String

good であるための条件が再帰的なので、再帰かな~という気持ちになる。

文字列の長さが 2 以上の場合に c-good であるための条件は

(i) cccc<(c+1)-good>

(ii) <(c+1)-good>cccc

の2通りがあって、これを a-good, b-good, c-good, ... について dfs で全探索する。 探索数は  n と同じくらいだから計算量は大丈夫。

書き換えなければいけない文字数を高速で計算するために、累積和を作って「区間における各アルファベットの登場回数」を求められるようにしておく。

dfs の実装ではセグ木を再帰で書いたときの経験が活きた。

提出コード

リンク:http://codeforces.com/contest/1385/submission/87133819

ll N;
string S;
ll ans;
vector<Vl> nums;
 
ll dfs(ll l, ll r, ll x) {
    if (r - l == 1) {
        int si = S[l] - 'a';
        return si != x;
    }
 
    ll mid = (l + r + 1) >> 1;
 
    ll t1 = dfs(mid, r, x + 1);
    ll t2 = dfs(l, mid, x + 1);
    ll k1 = (mid - l) - (nums[x][mid] - nums[x][l]);
    ll k2 = (r - mid) - (nums[x][r] - nums[x][mid]);
 
    return min(t1 + k1, t2 + k2);
}
 
 
void solve() {
    int Q;
    cin >> Q;
    while (Q--) {
        cin >> N;
        cin >> S;
        nums.assign(26, Vl(N + 1));
        for (ll i = 0; i < 26; i++) {
            for (ll j = 0; j < N; j++) {
                int si = S[j] - 'a';
                nums[i][j + 1] = nums[i][j] + (si == i);
            }
        }
        ll ans = dfs(0, N, 0);
        cout << ans << "\n";
    }
}

E - Directing Edges

サンプルを見るとトポロジカルソートかな~という気持ちになる。

ます、有向辺のみでグラフを構成しトポロジカルソートを試みる。閉路があって失敗したときは NO。

トポロジカルソートに成功すれば YES で、 無向辺をトポロジカル順序の上位から下位へ向きづければ閉路はできない。

提出コード

リンク:http://codeforces.com/contest/1385/submission/87145244

void solve() {
    int Q;
    cin >> Q;
    using Graph = vector<Vi>;
    Graph gd, gud;
    set<Pii> used;
    Vi indeg;
    Vi topo;
    vector<Pii> udes;
    while (Q--) {
        ll N, M;
        cin >> N >> M;
        ll ne = 0;
        gd.assign(N, Vi());
        gud.assign(N, Vi());
        indeg.assign(N, 0);
        for (ll i = 0; i < M; i++) {
            int t, u, v;
            cin >> t >> u >> v;
            --u, --v;
            if (t == 0) {
                gud[u].emplace_back(v);
                gud[v].emplace_back(u);
            }
            else {
                gd[u].emplace_back(v);
                ++indeg[v];
                ++ne;
            }
        }
 
        stack<int> st;
        topo.clear();
        for (ll i = 0; i < N; i++) {
            if (indeg[i] == 0) {
                st.emplace(i);
                topo.emplace_back(i);
            }
        }
 
        while (!st.empty()) {
            int u = st.top();
            st.pop();
            for (auto v : gd[u]) {
                --indeg[v];
                --ne;
                if (indeg[v] == 0) {
                    st.emplace(v);
                    topo.emplace_back(v);
                }
            }
        }
 
        if (ne > 0) {
            cout << "NO\n";
            continue;
        }
 
        used.clear();
        udes.clear();
        for (ll i = 0; i < N; i++) {
            int u = topo[i];
            for (auto v : gud[u]) {
                int x, y;
                x = u, y = v;
                if (x > y) swap(x, y);
                if (used.find({x, y}) == used.end()) {
                    udes.emplace_back(u, v);
                    used.emplace(x, y);
                }
            }
        }
 
        cout << "YES\n";
        for (ll i = 0; i < N; i++) {
            for (auto v : gd[i]) {
                cout << i + 1 << " " << v + 1 << "\n";
            }
        }
        for (auto [u, v] : udes) {
            cout << u + 1 << " " << v + 1 << "\n";
        }
    }
}

F - Removing Leaves

解けなかった。全方位木DPの雰囲気があるけどどうなんだろ......。後で時間があるときに解こうと思う。

G - Columns Swaps

見てない。