题解 P2633 【Count on a tree】


题目链接

我可能连普及水平都没有了.这题和[NOI2018]归程我都写炸了倍增LCA,以为主席树+树上差分(Kruskal重构树)写炸调了一晚上,总觉得还可以做些啥来抢救一下

Solution Count on a tree

题目大意:给定一颗树,每个点有一个点权,询问两点简单路径上第\(k\)小的点权.强制在线

主席树,树上差分


分析:看到第\(k\)小,我们想到什么?主席树

很多树上的问题我们都可以从它在序列上的做法入手得到启示.在序列上,我们用权值线段树来保存元素,然后用可持久化的方式来保存序列.本质上我们是做了一个前缀和,因为权值线段树满足可加/减性,所以我们可以通过差分的方式来以区间\([1,l - 1],[1,r]\)的权值线段树得到区间\([l,r]\)权值线段树

回到树上.我们从位置\(1\)开始做前缀和,那树上的"位置\(1\)"是什么?根节点.

我们可以用保存每一个点到根节点路径上点权的权值线段树的前缀和.同样差分求解.对于两点\(u,v\)我们如果直接将它们的前缀和加起来是不行的,这样会多算一部分,而多算的那一部分就是它们\(LCA\)的前缀和以及\(LCA\)父亲的前缀和

记点\(u\)到根节点路径点权的权值线段树的前缀和为\(d[u]\),那么最终我们要求的线段树就是\(d[u] + d[v] - d[lca(u,v)] - d[faz[lca(u,v)]]\) 可以一次\(dfs\)求出来

套个树上倍增就好然后我愉快写挂了

注意几点:

  • 此题\(RE\)多半不是你程序有什么大问题,而是\(WA\)了后由于强制在线\(xor\)出一个很大的点编号导致\(RE\)
  • 点权要离散化

上蒟蒻的代码:

#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 1e5 + 100;
int n,m,siz;
vector<int> G[maxn];
inline void addedge(int u,int v){
    G[u].push_back(v);
}
namespace ST{//主席树
    struct Node{
        int ls,rs,val;
    }tree[20000000];
    int root[maxn],cnt;
    inline int newnode(){return ++cnt;}
    inline int get(int x){return x == 0 ? 0 : tree[x].val;}
    inline void maintain(int root){tree[root].val = get(tree[root].ls) + get(tree[root].rs);}
    void add(int pos,int last,int &root,int l = 1,int r = siz){
        root = newnode();
        if(l == r){
            tree[root].val = tree[last].val + 1;
            return;
        }
        int mid = (l + r) >> 1;
        if(pos <= mid)add(pos,tree[last].ls,tree[root].ls,l,mid),tree[root].rs = tree[last].rs;
        else add(pos,tree[last].rs,tree[root].rs,mid + 1,r),tree[root].ls = tree[last].ls;
        maintain(root);
    }
    int query(int k,int add1,int add2,int del1,int del2,int l = 1,int r = siz){//差分
        if(l == r)return l;
        int mid = (l + r) >> 1,lsiz = get(tree[add1].ls) + get(tree[add2].ls) - get(tree[del1].ls) - get(tree[del2].ls);
        if(lsiz >= k)return query(k,tree[add1].ls,tree[add2].ls,tree[del1].ls,tree[del2].ls,l,mid);
        else return query(k - lsiz,tree[add1].rs,tree[add2].rs,tree[del1].rs,tree[del2].rs,mid + 1,r);
    }
}
const int maxdep = 25;
int val[maxn],tmp[maxn],faz[maxn][maxdep + 1],dep[maxn];
inline int rnk(int x){return lower_bound(tmp + 1,tmp + 1 + siz,x) - tmp;}//离散化
void dfs(int u){//预处理
    for(int i = 1;i <= maxdep;i++)//倍增
        faz[u][i] = faz[faz[u][i - 1]][i - 1];
    ST::add(rnk(val[u]),ST::root[faz[u][0]],ST::root[u]);//由于是到根节点的前缀和,所以上一个版本是它的父亲
    for(int v : G[u])
        if(v != faz[u][0])
            faz[v][0] = u,dep[v] = dep[u] + 1,dfs(v);
}
inline int lca(int x,int y){//屡次写挂的LCA /kk
    if(dep[x] > dep[y])swap(x,y);
    for(int i = maxdep;i >= 0;i--)
        if(dep[faz[y][i]] >= dep[x])
            y = faz[y][i];
    if(x == y)return x;
    for(int i = maxdep;i >= 0;i--)
        if(faz[x][i] != faz[y][i])
            x = faz[x][i],y = faz[y][i];
    return faz[x][0];
}
inline int getans(int u,int v,int k){//查询
    int l = lca(u,v);
    int pos = ST::query(k,ST::root[u],ST::root[v],ST::root[l],ST::root[faz[l][0]]);
    return tmp[pos];
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i = 1;i <= n;i++)
        scanf("%d",&val[i]),tmp[i] = val[i];
    for(int a,b,i = 1;i <= n - 1;i++)
        scanf("%d %d",&a,&b),addedge(a,b),addedge(b,a);
    sort(tmp + 1,tmp + 1 + n),siz = unique(tmp + 1,tmp + 1 + n) - tmp - 1;//离散化
    dep[1] = 1;
    dfs(1);
    for(int lastans = 0,u,v,k,i = 1;i <= m;i++){
        scanf("%d %d %d",&u,&v,&k);
        printf("%d\n",lastans = getans(u ^ lastans,v,k));
    }
    return 0;
}
优质内容筛选与推荐>>
1、UOJ #149. 【NOIP2015】子串
2、Oooooooo AAAAE
3、杭电1220--Cube
4、领导力的三个雷区
5、 SVN服务器配置(svn1.4.6+apache2.2.8 no ssl)


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号