読者です 読者をやめる 読者になる 読者になる

mayoko’s diary

プロコンとかいろいろ。

AIM Tech Round 3 (Div. 1) C. Centroids

問題

codeforces.com

頂点数 n の木 T 上の点 v が T の centorid であるとは, T から v を取り除いて得られる任意の連結成分が, 全て頂点数 n/2 以下であることを言う。

各頂点について, その頂点が以下の操作を一回以下許したとき centroid になるかどうかを判定せよ。
操作:木のある辺を消して, 好きな辺を加える(ただし新しいグラフも連結でなければならない)。

解法

操作を許さない場合にどうなるかを考えます。この場合はかなり単純です。頂点 v から伸びる辺の頂点から構成される部分木が, 全て n/2 以下の連結成分で構成されているか判定すれば良いです。いちいち頂点 v を根にして木dp しているとまにあわないですが, 0 を頂点にした木のみを考えて, 親方向側の連結成分の個数は n - (v の部分木のサイズ) というようにすれば良いです。

次に操作を許す場合を考えます。頂点 v の部分木でサイズが n/2 より大きいものがあった場合, その部分木の一部を切り取って v に直接つなぎかえるのが最適です。ここで部分木の頂点 ch で 2 通りの場合分けをします。

0 を根とした木でも ch が v の子である場合
この場合は, ch 以下の部分木を単純に調べるだけで良いので, euler_tour とか用意しておいてその中で部分木のサイズが n/2 である最大のものを求めます。ch の部分木のサイズ - この部分木のサイズ が n/2 以下なら操作をして centroid になることになります。

0 を根とした木で ch が v の親である場合
この場合がややこしいです。さらに二通りに分けます。

・つなげる木が v の祖先である場合
この場合, v からつなぎなおす頂点を a とすると, v - ch の木は,

  • (a の部分木サイズ) - (v の部分木サイズ) の木
  • n - (a の部分木サイズ) の木

に別れることがわかります。 n - (a の部分木サイズ) が n/2 以下で最大のものを覚えておけばこの中で (a の部分木サイズ) - (v の部分木サイズ) が n/2 以下になるものがあるかは, O(1) でわかります。

・つなげる木が v の祖先でない場合(要するに v の祖先の子孫のうち v の部分木以外のやつ)
木dp するとき dfs から抜け出すタイミングで部分木のサイズのメモしていくと, 頂点 v にたどり着いたときにメモされているのは, v 祖先の子孫の部分木のサイズのみになっています(直接の先祖はまだ v が子にあるので dfs から抜けておらず, サイズがメモされていない)。

よって, 部分木のサイズが n/2 以下なら 部分木のサイズをメモする, というようにやれば同じようにできます。

この手法でやるときは一回 dfs した後もう一回グラフを逆順にして再び dfs しなければいけないことに注意です。

// セグメント木(RMQ 対応)
// update: k 番目の値を a に変更
// query: [l, r) の区間の最大値を求める
template<typename T>
struct ST {
    vector<T> seg;
    int size;
    ST(int n) {
        size = 1;
        while (size < n) size *= 2;
        seg.resize(2*size-1, 0);
    }
    inline T merge(T x, T y) {
        return max(x, y);
    }
    void update(int k, T a) {
        k += size-1;
        seg[k] = a;
        while (k > 0) {
            k = (k-1)/2;
            seg[k] = merge(seg[k*2+1], seg[k*2+2]);
        }
    }
    T query(int a, int b, int k, int l, int r) {
        if (r <= a || b <= l) return 0;
        if (a <= l && r <= b) return seg[k];
        T vl = query(a, b, k*2+1, l, (l+r)/2);
        T vr = query(a, b, k*2+2, (l+r)/2, r);
        return merge(vl, vr);
    }
    T query(int a, int b) {
        return query(a, b, 0, 0, size);
    }
};

const int MAXN = 400400;
vi G[MAXN];

// Euler-Tour
const int MAXSIZE = MAXN;
int BEGIN[MAXSIZE], END[MAXSIZE];
vector<int> euler_tour;
int K;

void createEulerTour(int v, int p) {
    BEGIN[v] = K++;
    euler_tour.push_back(v);
    for (int el : G[v]) {
        if (el == p) continue;
        createEulerTour(el, v);
        euler_tour.push_back(v);
        K++;
    }
    END[v] = K;
}

int subSize[MAXN];
pii maxi[MAXN];
int n;

int getSize(int v, int p) {
    int& ret = subSize[v];
    pii& ma = maxi[v];
    ma.first = 0, ma.second = -1;
    for (int ch : G[v]) if (ch != p) {
        int tmp = getSize(ch, v);
        if (ma.first < tmp) {
            ma.first = tmp;
            ma.second = ch;
        }
        ret += tmp;
    }
    ret++;
    int tmp = n - ret;
    if (ma.first < tmp) {
        ma.first = tmp;
        ma.second = p;
    }
    return ret;
}

int ans[MAXN];
int tsize;

void dfs(int v, int p, ST<int>& seg, int top) {
    //cout << v << " " << top << " " << tsize << endl;
    if (maxi[v].second == p) {
        if (subSize[top]-subSize[v] <= n/2) ans[v] = 1;
        else if (n-subSize[v]-tsize <= n/2) ans[v] = 1;
    } else {
        int ch = maxi[v].second;
        int tmp = seg.query(BEGIN[ch]+1, END[ch]);
        if (maxi[v].first - tmp <= n/2) ans[v] = 1;
    }
    if (n - subSize[v] <= n/2) top = v;
    for (int ch : G[v]) if (ch != p) {
        dfs(ch, v, seg, top);
    }
    if (subSize[v] <= n/2) tsize = max(tsize, subSize[v]);
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 0; i < n-1; i++) {
        int a, b;
        cin >> a >> b;
        a--; b--;
        G[a].push_back(b);
        G[b].push_back(a);
    }
    // 部分木のサイズを求める
    getSize(0, -1);
    // euler tour して部分木のサイズをメモ
    createEulerTour(0, -1);
    ST<int> seg(euler_tour.size());
    for (int i = 0; i < n; i++) {
        if (subSize[i] > n/2) {
            seg.update(BEGIN[i], 0);
        } else {
            seg.update(BEGIN[i], subSize[i]);
        }
    }
    dfs(0, -1, seg, 0);
    tsize = 0;
    for (int i = 0; i < n; i++)
        reverse(G[i].begin(), G[i].end());
    dfs(0, -1, seg, 0);
    for (int i = 0; i < n; i++) {
        cout << ans[i] ;
        if (i < n-1) cout << " ";
    }
    cout << endl;
    return 0;
}