mayoko’s diary

プロコンとかいろいろ。

Codeforces Round #223 (Div. 1) B. Sereja and Tree

解法

この問題では N, M が比較的小さいので, 一つのクエリにつき O(N log N) 程度で処理できれば AC します。

まず, 7000 行目で頂点がいくつぐらいあるのかを確認してみましょう。プログラムに計算させると 109315 個であるとわかりました。
type 2 のクエリに対しては, 今までの type 1 のクエリそれぞれに対して, 「level t の v 番目の頂点を根とする部分木が, 区間 [l, r] を含んでいるか」というのを判定します。
左から v 番目の頂点から, 深さ d だけ降りると, 左から何番目まで v の部分木の頂点の右端になるか(f(v, d) とする), というのは, v と d のみの関数です。これに注目すると, ダブリングのテクニックで f(v, d) は O(log d) で計算できます。parent[k][v] = (v から 2^k だけ降りた時, v の部分木の右端はいくつになるか)というのを前準備で求めまて(上で言ったことにより, v の上限は 1.1*10^5 ぐらいで OK) d に対してほげするやつです。

f(v-1, d), f(v, d) の分布がいい感じになっていたら集合に値を加えます。

const int MAX = 120000;
const int MAXQ = 7070;
int parent[15][MAX];

int level[MAXQ], L[MAXQ], R[MAXQ], X[MAXQ];

int calc(int v, int d) {
    for (int i = 15-1; i >= 0; i--) {
        if (d&(1<<i)) v = parent[i][v];
    }
    return v;
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    for (int i = 0; i < MAX; i++) {
        int tmp = 1, next = i;
        while (tmp <= i) {
            next++;
            tmp *= 2;
        }
        parent[0][i] = next;
    }
    for (int i = 0; i < 15-1; i++) {
        for (int j = 0; j < MAX; j++) {
            if (parent[i][j] > MAX) continue;
            else parent[i+1][j] = parent[i][parent[i][j]];
        }
    }
    int n, m;
    cin >> n >> m;
    int cnt = 0;
    for (int i = 0; i < m; i++) {
        int type;
        cin >> type;
        if (type == 1) {
            cin >> level[cnt] >> L[cnt] >> R[cnt] >> X[cnt];
            cnt++;
        } else {
            int t, v;
            cin >> t >> v;
            set<int> S;
            for (int j = 0; j < cnt; j++) {
                int d = level[j]-t;
                if (d < 0) continue;
                int t1 = calc(v-1, d), t2 = calc(v, d);
                if (t1+1 <= R[j] && L[j] <= t2) S.insert(X[j]);
            }
            cout << S.size() << endl;
        }
    }
    return 0;
}