[COCI2020-2021#3] Specijacija 题解

· · 题解

提供一个分块的做法。

考虑把每一个点表示成 (level,number)level 表示这个点所在的层数, number 表示这个点是这一层的第几个点。

同理可以把 a 数组,和询问的 x,y 都表示成这种形式。

考虑如果点 x 移动到 x 的父亲,那么 level 肯定减一,而只有 a_{i}<number 时,number 会减一,否则不会变。也就是说:

number-=(a[i]<number).

考虑对 a 序列分块,设 trans_{x,v} 表示第 x 个块,进入这个块之前numberv,统计过这个块之后,number 是多少。

那么借助 trans 数组,我们就可以仿照求 lca 的过程,如果能跳过一个块,就跳过一个块,否则就一层一层跳,显然可以在 O(q\sqrt n)。的时间内求出 lca 。

于是只需求出 trans 数组就行了。

考虑增量计算,由 trans_{x,v} 得到 trans_{x,v+1}

设使得 number 减少的位置为关键点,那么一个性质就是说增量的时候要么新增一个关键点,要么保持不变,原来就是关键点的现在一定还是关键点。

考虑怎么求出新增加的关键点,考虑设 pre_i 表示 i 之前选的关键点个数。

那么一个位置可以成为关键点,当且仅当 a_i<v+1-pre_i

那么新增加的就是最靠右的一个符合这个条件的位置。

这个条件也就是:

a_i+pre_i<v+1

用一个线段树维护 a_i+pre_i

那么显然只需要支持区间加就可以了,查询的时候只需要判断最靠前的一个位置的值是否小于 v+1 就行了。

复杂度是 O(n\sqrt n \log n)

考虑优化一下,因为最多就只会把这个块全选完,因此变化量是 O(\sqrt n) 的,这意味着有大量的位置的 trans 是相等的,可以之间跳过去。

复杂度 O(n\sqrt n+n\log n)

不过因为常数比较大,实测设块长为 1700 比较优秀。

不过本人实现不好,必须开 O2 才能通过。

const int BL = 220;
int tr[BL][N];
int dec[N];
int bel[N], L[N], R[N];
int B;
LL tot[N];
int Min[N * 4], tag[N * 4], Pos[N * 4];
void pushup(int k) {
    Min[k] = Min[k << 1 | 1];
    Pos[k] = Pos[k << 1 | 1];
    if (Min[k << 1] < Min[k << 1 | 1])
        Min[k] = Min[k << 1], Pos[k] = Pos[k << 1];
}
void Build(int k, int l, int r) {
    tag[k] = 0;
    if (l == r) {
        Min[k] = dec[l];
        Pos[k] = l;
        return;
    }
    int mid = (l + r) >> 1;
    Build(k << 1, l, mid);
    Build(k << 1 | 1, mid + 1, r);
    pushup(k);
}
void Push(int k, int v) {
    tag[k] += v;
    Min[k] += v;
}
void pushdown(int k) {
    if (tag[k]) {
        Push(k << 1, tag[k]);
        Push(k << 1 | 1, tag[k]);
        tag[k] = 0;
    }
}
void Addate(int k, int l, int r, int L, int R) {
    if (L <= l && r <= R) {
        Push(k, 1);
        return;
    }
    pushdown(k);
    int mid = (l + r) >> 1;
    if (L <= mid)
        Addate(k << 1, l, mid, L, R);
    if (R > mid)
        Addate(k << 1 | 1, mid + 1, r, L, R);
    pushup(k);
}
void Ban(int k, int l, int r, int x) {
    if (l == r) {
        Min[k] = 1e9;
        return;
    }
    pushdown(k);
    int mid = (l + r) >> 1;
    if (x <= mid)
        Ban(k << 1, l, mid, x);
    else
        Ban(k << 1 | 1, mid + 1, r, x);
    pushup(k);
}
void rebuild(int x) {
    Build(1, L[x], R[x]);
    int len = 0;
    for (int i = 1; i <= n + 1; i++) {
        int o = Min[1];
        if (o >= 1e9) {
            for (; i <= n + 1; i++) tr[x][i] = i - len;
            break;
        }
        for (int j = i; j <= o; j++) tr[x][j] = j - len;
        len++;
        tr[x][o + 1] = o + 1 - len;
        int p = Pos[1];
        Ban(1, L[x], R[x], p);
        Addate(1, L[x], R[x], L[x], p);
        i = o + 1;
    }
}
int pos(LL x) {
    int l = 0, r = n, mid, ans;
    while (l <= r) {
        mid = (l + r) >> 1;
        if (tot[mid] < x)
            ans = mid, l = mid + 1;
        else
            r = mid - 1;
    }
    return ans + 1;
}
inline int reduce(int x, int l, int r) {
    for (int i = r - 1; i >= l; i--) x -= (dec[i] < x);
    return x;
}
int LittleFools(int i, int x) { return reduce(x, L[bel[i]], i); }
LL LittleFind(int i, int x, int y) {
    for (i--; x != y && i >= 1; i--) {
        x -= (dec[i] < x);
        y -= (dec[i] < y);
    }
    return tot[i] + x;
}
inline LL LCA(int i, int x, int j, int y) {
    while (abs(bel[i] - bel[j]) > 1) {
        if (bel[i] < bel[j])
            swap(i, j), swap(x, y);
        if (i != L[bel[i]])
            x = LittleFools(i, x), i = L[bel[i]];
        i = L[bel[i] - 1], x = tr[bel[i]][x];
    }
    if (i < j)
        swap(i, j), swap(x, y);
    x = reduce(x, j, i);
    i = j;
    int fi = LittleFools(i, x), fj = LittleFools(j, y);
    bool fl = 0;
    while (fi != fj) {
        if (!fl)
            i = L[bel[i]], j = i;
        else
            i = L[bel[i] - 1], j = i;
        x = fi;
        y = fj;
        fl = 1;
        fi = tr[bel[i] - 1][x];
        fj = tr[bel[j] - 1][y];
    }
    return LittleFind(i, x, y);
}
void solve() {
    B = 1700;
    for (int i = 1; i <= n; i++) {
        dec[i] = a[i] - tot[i - 1];
        tot[i] = tot[i - 1] + i;
    }
    for (int i = 1; i <= n; i++) {
        bel[i] = (i - 1) / B + 1;
        if (!L[bel[i]])
            L[bel[i]] = i;
        R[bel[i]] = i;
    }
    int cnt = bel[n];
    bel[n + 1] = cnt + 1;
    L[bel[n + 1]] = n + 1;
    for (int i = 1; i <= cnt; i++) rebuild(i);
    LL sum = 1ll * (n + 1) * (n + 2) / 2;
    LL ans = 0;
    while (m--) {
        LL x, y;
        read(x);
        read(y);
        x = (x - 1 + 1ll * tp * ans) % sum + 1;
        y = (y - 1 + 1ll * tp * ans) % sum + 1;
        int i = pos(x), u = x - tot[i - 1];
        int j = pos(y), v = y - tot[j - 1];
        ans = LCA(i, u, j, v);
        printf("%lld\n", ans);
    }
}