「Luogu5346」「XR-1」柯南家族-替罪羊树+主席树

博客围绕聪明程度排序问题展开,指出聪明程度比较有传递性,若能快速排序就能回答询问。最初考虑用平衡树维护序列,但复杂度高无法通过。后提出优化方案,为每人赋[0,1]实数权值,用替罪羊树重构时平均分配权值,避免炸精度,询问是主席树基本操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

这是链接

Solution

这里写的是 @租酥雨 在考场的写法。

显然,聪明程度的比较具有传递性。所以如果我们可以快速把每个人的聪明程度排序,然后就可以轻易的回答询问了。

考虑按找某种顺序往已经排好的序列添加一个数,满足它的父亲已经被比较了(比如直接按输入顺序)。

比较显然的一种方法是用平衡树维护这个序列,每次往平衡树中添加一个点。但是把一个点插入平衡树需要 O ( l o g n ) O(logn) O(logn)次操作,每次比较是 O ( l o g n ) O(logn) O(logn)的,总复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n),加上平衡树的大常数显然无法通过。

考虑优化比较的复杂度。我们为每一个人赋上一个 [ 0 , 1 ] [0,1] [0,1]的实数权值,用这个来表示一个人的聪明程度,新加的人的权值为他前驱后继的平均值。但是直接这样会炸精度,所以我们考虑用替罪羊树,在重构的同时重构把权值平均分配。这样由于平衡树的树高是 O ( l o g n ) O(logn) O(logn)的,所以我们 / 2 /2 /2的次数也是 O ( l o g n ) O(logn) O(logn)的,就不会炸精度了。

至于询问是主席树基本操作就不讲了。

顺便说一句,小兰没有发现柯南家族的人都是一个人就可以繁殖后代吗?

#include <bits/stdc++.h>
using namespace std;

inline int gi()
{
    char c = getchar();
    while (c < '0' || c > '9') c = getchar();
    int sum = 0;
    while ('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
    return sum;
}

const int maxn = 500005;
const double alpha = 0.7;

int n, q, fa[maxn], c[maxn];

int rt, lch[maxn], rch[maxn], siz[maxn], cnt, tmp[maxn];
double w[maxn];

void dfs(int u)
{
    if (lch[u]) dfs(lch[u]);
    tmp[++cnt] = u;
    if (rch[u]) dfs(rch[u]);
}

void build(int &u, int l, int r, double wl, double wr)
{
    int mid = (l + r) >> 1;
    u = tmp[mid]; w[u] = (wl + wr) / 2;
    if (l < mid) build(lch[u], l, mid - 1, wl, w[u]); else lch[u] = 0;
    if (mid < r) build(rch[u], mid + 1, r, w[u], wr); else rch[u] = 0;
    siz[u] = 1 + siz[lch[u]] + siz[rch[u]];
}

void rebuild(int &u, double l, double r) {cnt = 0; dfs(u); build(u, 1, cnt, l, r);}

inline bool cmp(int a, int b)
{
    if (c[a] != c[b]) return c[a] > c[b];
    else if (fa[a] && fa[b] && fa[a] != fa[b]) return w[fa[a]] < w[fa[b]];
    else return a > b;
}

void insert(int &u, int x, double l, double r, bool f)
{
    if (!u) return u = x, siz[u] = 1, w[x] = (l + r) / 2, void();
    ++siz[u];
    bool flg = 0;
    if (cmp(x, u)) {
        flg |= alpha * (siz[lch[u]] + 1) > siz[rch[u]];
        insert(lch[u], x, l, w[u], flg);
    } else {
        flg |= alpha * (siz[rch[u]] + 1) > siz[lch[u]];
        insert(rch[u], x, w[u], r, flg);
    }
    if (flg && !f) rebuild(u, l, r);
}

struct edge
{
    int to, next;
} e[maxn];
int h[maxn], rk[maxn], dfn[maxn], low[maxn], T, ecnt;

inline void add(int u, int v) {e[++ecnt] = (edge) {v, h[u]}; h[u] = ecnt;}

struct node
{
    int lch, rch, siz;
} s[maxn * 60];
int tot, rt1[maxn], rt2[maxn];

void modify(int &x, int l, int r, int p)
{
    ++tot; s[tot] = s[x]; ++s[x = tot].siz;
    if (l == r) return ;
    int mid = (l + r) >> 1;
    if (p <= mid) modify(s[x].lch, l, mid, p);
    else modify(s[x].rch, mid + 1, r, p);
}

int query(int x, int y, int l, int r, int k)
{
    if (l == r) return tmp[l];
    int mid = (l + r) >> 1;
    if (k > s[s[y].lch].siz - s[s[x].lch].siz) return query(s[x].rch, s[y].rch, mid + 1, r, k - (s[s[y].lch].siz - s[s[x].lch].siz));
    else return query(s[x].lch, s[y].lch, l, mid, k);
}

void t_dfs(int u)
{
    dfn[u] = ++T;
    rt1[u] = rt1[fa[u]]; modify(rt1[u], 1, n, rk[u]);
    rt2[T] = rt2[T - 1]; modify(rt2[T], 1, n, rk[u]);
    for (int i = h[u], v; v = e[i].to, i; i = e[i].next)
        if (v != fa[u]) t_dfs(v);
    low[u] = T;
}

int main()
{
    n = gi(); q = gi();
    for (int i = 2; i <= n; ++i) add(fa[i] = gi(), i);
    for (int i = 1; i <= n; ++i) {
        c[i] = gi();
        insert(rt, i, 0, 1, 0);
    }

    rebuild(rt, 0, 1);
    for (int i = 1; i <= n; ++i) rk[tmp[i]] = i;
    
    t_dfs(1);

    int tp, x;
    while (q--) {
        tp = gi(); x = gi();
        if (tp == 1) printf("%d\n", rk[x]);
        else if (tp == 2) printf("%d\n", query(0, rt1[x], 1, n, gi()));
        else printf("%d\n", query(rt2[dfn[x] - 1], rt2[low[x]], 1, n, gi()));
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值