洛谷P2633 Count on a tree 题解

题面

题目描述

给定一棵 \(n\) 个节点的树,每个点有一个权值。有 \(m\) 个询问,每次给你 \(u,v,k\),你需要回答 \(u \text{ xor last}\)\(v\) 这两个节点间第 \(k\) 小的点权。

其中 \(\text{last}\) 是上一个询问的答案,定义其初始为 \(0\),即第一个询问的 \(u\) 是明文。

输入格式

第一行两个整数 \(n,m\)

第二行有 \(n\) 个整数,其中第 \(i\) 个整数表示点 \(i\) 的权值。

后面 \(n-1\) 行每行两个整数 \(x,y\),表示点 \(x\) 到点 \(y\) 有一条边。

最后 \(m\) 行每行三个整数 \(u,v,k\),表示一组询问。

输出格式

\(m\) 行,每行一个正整数表示每个询问的答案。

数据范围

对于 \(100\%\) 的数据,\(1\le n,m \le 10^5\),点权在 \([1, 2 ^ {31} - 1]\) 之间。

解决办法

第一感觉

  • 注意到求区间第 \(k\) 小的点权,立马想到 主席树 维护。

  • 再者维护树上一条链的答案,首先想到 树链剖分,但是注意到实际上我们建立的是权值线段树,需要求的仅仅是 \((u,v)\)\(lca\) ,因此我们也可以使用 倍增求 \(lca\)

  • 注意到数据范围要离散化

具体实现

  • 我们遍历这棵树,以每个点到根节点的简单路径建立权值线段树存入主席树中

具体地: 我们在dfs的过程中以父节点为上一版本更新子节点为下一版本

  • 使用 树上差分 的思想 替换普通 主席树 中的普通 差分 思想

具体地: \((u,v)\) 的主席树即为 \(t_u + t_v - t_{lca} - t_{pre_{lca}}\) ( \(pre_{lca}\) 即为 \(lca\) 的父节点)

  • 使用 树链剖分倍增法\(lca\)

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100005;

int rd(){}//快读

int n,m;
int a[N],olda[N],root[N],len;

vector<int> adj[N];

#define mid ((pl + pr) >> 1)
struct node
{
    int ls,rs,sum;
}t[N << 5];

int tot;

int build(int pl,int pr)
{
    int rt = ++tot;
    t[rt].sum = 0;
    if(pl == pr) return rt;
    t[rt].ls = build(pl,mid);
    t[rt].rs = build(mid + 1,pr);
    return rt;
}

int upd(int p,int pl,int pr,int lr)
{
    int rt = ++tot;
    t[rt] = t[p];
    t[rt].sum++;
    if(pl == pr) return rt;
    if(lr <= mid) t[rt].ls = upd(t[rt].ls,pl,mid,lr);
    else t[rt].rs = upd(t[rt].rs,mid + 1,pr,lr);
    return rt;
}

int qry(int pl,int pr,int u,int v,int lca,int lcafa,int k)
{
    if(pl == pr) return pl;
    int x = t[t[u].ls].sum + t[t[v].ls].sum - t[t[lca].ls].sum - t[t[lcafa].ls].sum;
    if(x >= k) return qry(pl,mid,t[u].ls,t[v].ls,t[lca].ls,t[lcafa].ls,k);
    else return qry(mid + 1,pr,t[u].rs,t[v].rs,t[lca].rs,t[lcafa].rs,k - x);
}

int dep[N],pre[N],siz[N],son[N];
void dfs1(int u,int fa)
{
    dep[u] = dep[fa] + 1;
    pre[u] = fa;
    siz[u] = 1;
    root[u] = upd(root[fa],1,len,a[u]);//写在树链剖分里和单独遍历是一样的
    for(auto v : adj[u])
    {
        if(v == fa) continue;
        dfs1(v,u);
        siz[u] += siz[v];
        if(siz[v] > siz[son[u]]) son[u] = v;
    }
}

int id[N],w[N],top[N],cnt;
void dfs2(int u,int topc)
{
    id[u] = ++cnt;
    w[cnt] = a[u];
    top[u] = topc;
    if(!son[u]) return;
    dfs2(son[u],topc);
    for(auto v : adj[u])
    {
        if(v == pre[u] || v == son[u]) continue;
        dfs2(v,v);
    }
}

int lca(int u,int v)
{
    while(top[u] != top[v])
    {
        if(dep[top[u]] < dep[top[v]]) swap(u,v);
        u = pre[top[u]];
    }
    if(dep[u] > dep[v]) swap(u,v);
    return u;
}

signed main()
{
    n = rd(),m = rd();
    for(int i = 1;i <= n;i++) a[i] = olda[i] = rd();
    for(int i = 1;i < n;i++)
    {
        int u = rd(),v = rd();
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    sort(olda + 1,olda + n + 1);//离散化
    len = unique(olda + 1,olda + n + 1) - olda - 1;
    for(int i = 1;i <= n;i++) a[i] = lower_bound(olda + 1,olda + len + 1,a[i]) - olda;
    root[0] = build(1,len);//建空树
    dfs1(1,0);
    dfs2(1,1);
    int ans = 0;
    while(m--)
    {
        int u = rd(),v = rd(),k = rd();
        u = u ^ ans;//根据题意操作
        int ca = lca(u,v);
        ans = olda[qry(1,len,root[u],root[v],root[ca],root[pre[ca]],k)];//离散化还原
        cout << ans << '\n';
    }
    return 0;
}

这个题是一个缝合怪,不好玩,但是练手还是不错的。

posted @ 2024-12-25 20:28  IC0CI  阅读(66)  评论(1)    收藏  举报