<-- home

AtCoder ABC 157

ABC157に参加した。自分用メモ。

A B C D E F
o o o o x x

A~Dは特に問題なく解けた。Eも解法は思いついたが、セグメント木の実装にバグがあり、RE(Runtime Error)。

D-Friend Suggestions

各人の友達候補の数を計算する問題。無向グラフのノードが人、エッジが友達関係/ブロック関係になっている。 「ある人物Aの友達候補がBである」とは、

  • Aから友達関係をたどってBに到達できる
  • AとBがブロック関係になっていない
  • AとBが異なる人物

と定義される。したがって、

が求める答えになる。(Aが含まれる連結成分: 友達関係の辺で構成される連結成分)

連結成分の求め方

2つ方法がある。

  • DFSを使う
  • Union-Find木を使う

今回はDFSを使ったが、Union-Find木を用いるほうがシンプルそう。あとで実装する。

[追記] 03/05/20

Union-Find木を用いる実装

template<typename T>
struct UnionFind {
    vector<T> parent;
    vector<i64> rank;

    UnionFind(i64 n) {
        parent.assign(n, 0);
        REP(i, n) parent[i] = i;
        rank.assign(n, 1);
    }

    T find(T t) {
        if (parent[t] == t) return t;
        return parent[t] = find(parent[t]);
    }

    bool unite(T t1, T t2) {
        t1 = find(t1);
        t2 = find(t2);
        if (t1 == t2) return false;

        if (rank[t1] < rank[t2]) swap(t1, t2);
        parent[t2] = t1;
        rank[t1] += rank[t2];
        return true;
    }

    bool same(T t1, T t2) {
        return find(t1) == find(t2);
    }
};


#define MAX_N 100'000
i64 N, M, K;
vector<i64> B[MAX_N]; // ブロック関係
i64 deg[MAX_N];

int main() {
    cin >> N >> M >> K;
    fill(deg, deg + N, 0);

    UnionFind<i64> uf(N);

    i64 u, v;
    REP(i, M) {
        cin >> u >> v;
        u--; v--;
        uf.unite(u, v);
        deg[u]++;
        deg[v]++;
    }
    REP(i, K) {
        cin >> u >> v;
        u--; v--;
        B[u].pb(v);
        B[v].pb(u);
    }

    REP(u, N) {
        i64 cnt = uf.rank[uf.find(u)] - 1 - deg[u];
        for (auto it = B[u].begin(); it != B[u].end(); it++) {
            if (uf.same(u, *it)) {
                cnt--;
            }
        }
        cout << cnt << " ";
    }
    cout << endl;

    return 0;
}

E-Simple String Queries

ある文字列が与えられ、それに対する2種類のクエリがN個発行される。

  • クエリ1: 文字列のk番目の文字を変更する
  • クエリ2: 文字列のl番目からr番目からなる部分文字列を取得する

セグメント木で、各ノードが、それに対応する区間に含まれる文字の集合をもつようにすれば、高速にクエリを処理できる。今回の問題では、文字の集合を管理するのに、set<char>を使ってもいいが、文字列は英小文字からなるという制限があるので、26bitのビットフラグで集合を管理すると、処理が早いし、コードも簡潔になる。

// セグメント木のテンプレートは
// https://ei1333.github.io/luzhiled/snippets/structure/segment-tree.html
// を参考にした。1-indexedから0-indexedに変更している。
template<typename Monoid>
struct SegmentTree {
    using F = function<Monoid(Monoid, Monoid)>;

    i64 sz;
    vector<Monoid> seg;

    const F f;
    const Monoid M1;

    SegmentTree(i64 n, const F f, const Monoid &M1) : f(f), M1(M1) {
        sz = 1;
        while (sz < n) sz *= 2;
        seg.assign(2 * sz - 1, M1);
    }

    void set(i64 k, const Monoid &x) {
        seg[k + sz - 1] = x;
    }

    void build() {
        for (i64 k = sz - 2; k >= 0; k--) {
            seg[k] = f(seg[2 * k + 1], seg[2 * k + 2]);
        }
    }

    void update(i64 k, const Monoid &x) {
        k += sz - 1;
        seg[k] = x;
        while (k > 0) {
            k = (k - 1) / 2;
            seg[k] = f(seg[2 * k + 1], seg[2 * k + 2]);
        }
    }

    Monoid query(i64 l, i64 r) {
        Monoid vl = M1, vr = M1;
        for (l += sz - 1, r += sz - 1; l < r; l = (l - 1) / 2, r = (r - 1) / 2) {
            if ((l & 1) == 0) vl = f(vl, seg[l++]);
            if ((r & 1) == 0) vr = f(seg[--r], vr);
        }
        return f(vl, vr);
    }

    Monoid operator[](const i64 &k) const {
        return seg[k + sz - 1];
    }
};

// 今回の問題では、英小文字を1 << (c - 'a')のようにbitflagにして、
// セグメント木を以下のように初期化する
// SegmentTree<i64> segtree(N, [](i64 x, i64 y){ return x | y; }, 0);