E-Eyjafjalla(2021牛客暑期多校训练营9)【主席树】

13 篇文章 0 订阅
8 篇文章 0 订阅

E-Eyjafjalla(2021牛客暑期多校训练营9)【主席树】

传送门

思路

以点1为根,可以发现这棵树有这样的性质:深度越深,温度越低。所以我们从当前点往根节点走,一直走到父亲节点的温度不在 [ l , r ] [l, r] [l,r] 区间内时停下,此时所在的节点就是病毒能够扩散到的深度最低的节点(这一步可以用倍增来做)。

以这个节点为根节点的子树包含了所有被感染的节点,也就是说这颗子树外的节点不可能被感染。被感染的条件是温度在区间 [ l , r ] [l, r] [l,r]​​ 之间,所以此时问题就转化为了找出这颗子树下有多少温度大于等于 l l l​​ 的节点,而主席树的基本操作就是统计某一权值区间内有多少节点,由此我们可以结合dfs序(或者树剖)将树上操作转化为区间操作,于是用倍增+主席树(dfs序建树)就能AC辽~

代码

比赛的时候觉得这用树剖做不是易如反掌,然后赛后写了一天写不出来,最后还是和队友一个方法做了qwq(队友yyds

#include <bits/stdc++.h>

using namespace std;

const int N = 4e5 + 10, M = 1e5 + 10;

int n, q;
int a[N], tot, dfstot;
vector<int> nums;
int head[N], es[N * 2], nxt[N], dfsl[N], dfsr[N], par[N][30];

struct query //询问
{
    int x, l, r;
}qs[N];

struct Node
{
    int l, r;
    int cnt;
} tr[N * 4 + M * 35];

int root[N], idx;

//离散化后找节点位置 -- l = find(l)
int find(int x)
{
    return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}

//建树 -- root[0] = build(0, num.size() - 1)
int build(int l, int r)
{
    int p = ++ idx;
    if (l == r)
        return p;
    int mid = l + r >> 1;
    tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);
    return p;
}

//修改 -- root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[x]));
// i:当前是第i个版本 x:要修改的值的位置
int insert(int p, int l, int r, int x)
{
    int q = ++ idx;
    tr[q] = tr[p];
    if (l == r)
    {
        tr[q].cnt ++ ;
        return q;
    }
    int mid = l + r >> 1;
    if (x <= mid)
        tr[q].l = insert(tr[p].l, l, mid, x);
    else
        tr[q].r = insert(tr[p].r, mid + 1, r, x);
    tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
    return q;
}

//查询x到y区间中范围在[s, t]之间的节点个数
//调用 query(root[x], root[y], s, t, 0, nums.size() - 1) 获得个数
int query(int x, int y, int s, int t, int l, int r)
{
    if (s <= l && r <= t)
    {
        return tr[y].cnt - tr[x].cnt;
    }

    int mid = (l + r) >> 1;
    int res = 0;
    if (s <= mid)
    {
        res += query(tr[x].l, tr[y].l, s, t, l, mid);
    }
    if (mid < t)
    {
        res += query(tr[x].r, tr[y].r, s, t, mid + 1, r);
    }
    return res;
}

//查询p到q区间中的第k大数
//调用 query(root[r], root[l - 1], 0, nums.size() - 1, k) 获得其数组位置
int query(int q, int p, int l, int r, int k)
{
    if (l == r) return r;
    int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
    int mid = l + r >> 1;
    if (k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k);
    else return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);
}

void addEdge(int u, int v)
{
    es[++idx] = v;
    nxt[idx] = head[u];
    head[u] = idx;
}

//边dfs边建树
void dfs(int u, int fa)
{
    dfsl[u] = ++dfstot; //dfsl维护了这颗子树dfs序的最小值
    root[tot + 1] = insert(root[tot], 0, nums.size() - 1, find(a[u])); //建树
    tot++; //第tot个版本
    par[u][0] = fa;
    for(int i = 1; i < 20; i++)
        par[u][i] = par[par[u][i - 1]][i - 1];//倍增
    
    for(int i = head[u]; ~i; i = nxt[i])
    {
        int v = es[i];
        if(v == fa)
            continue;
        dfs(v, u);
    }
    dfsr[u] = dfstot; //dfsr维护了这颗子树dfs序的最大值
    //得到dfsl与dfsr后,这颗子树就变成了一个区间,也就是[dfsl[u], dfsr[u]]这个区间
}

void init()
{
    memset(head, -1, sizeof head);
}

int cnt;

int main()
{
    init();
    scanf("%d", &n);
    for(int i = 0, u, v; i < n - 1; i++)
    {
        scanf("%d%d", &u, &v);
        addEdge(u, v);
        addEdge(v, u);
    }
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &a[i]);
        nums.push_back(a[i]);
    }

    scanf("%d", &q);
    for(int i = 1; i <= q; i++)
    {
        int x, l, r;
        scanf("%d%d%d", &x, &l, &r);
        qs[i] = {x, l, r};
        //将l, r也加入nums(因为是权值线段树)
        nums.push_back(l);
        nums.push_back(r);
    }

    //离散化处理
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());

    root[0] = build(0, nums.size() - 1); //初始版本

    dfs(1, -1); //dfs并建树

    for(int i = 1; i <= q; i++)
    {
        int x = qs[i].x;
        int l = qs[i].l;
        int r = qs[i].r;
        if(a[x] < l || a[x] > r) //特判,如果第一个节点就不符合直接退出
        {
            printf("0\n");
            continue;
        }
        for(int i = 19; i >= 0; i--)
            if(par[x][i] > 0 && a[par[x][i]] <= r)
                x = par[x][i]; //从当前点往根节点走,一直走到父亲节点的温度不在[l, r]区间内时停下
        l = find(l); //找离散化数组中的对应位置
        r = find(r);
        printf("%d\n", query(root[dfsl[x] - 1], root[dfsr[x]], l, r, 0, nums.size() - 1)); //r的版本减去l - 1的版本,也就是得到了值在l到r之间的数的个数
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值