SPOJ 10628. Count on a tree (树上第k大,LCA+主席树)

题目链接:点击打开链接

题目大意:给你一颗n个结点的树,每个结点都有一个权值。

为从结点u到结点v这条路径上所有的权值第k大是多少

思路:主要思路是  树上第k大,LCA+主席树

为什么要用到LCA?

因为u到v的路径必定是从u走到他们的LCA(u,v)点,然后从LCA(u,v)点走到v。

所以我们需要提前求出这棵树的LCA

主席树怎么建树?

每一棵线段树都是每个结点继承其父节点的线段树加上自己的结点。

如此对于一条路径就可以统计从其祖先结点到其子节点这条路径上增加了哪些数

在主席树路径上找第k大时,即统计LCA(u,v)到u和v两条路径上总共出现了【l,r】区间内的数字有多少个,大于k往左子树,小于k往右子树,和普通的主席树找第k大相同

注意一下,因为lca(u,v)是u,v的交点,统计时候如果被统计两次,需要减去一次。

代码参考kuangbin模板:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
//主席树部分 *****************8
const int MAXN = 400010;
const int M = MAXN * 40;
int n,q,m,TOT;
int a[MAXN], t[MAXN];
int T[M], lson[M], rson[M], c[M];

void Init_hashs()
{
    for(int i = 1; i <= n;i++)
        t[i] = a[i];
    sort(t+1,t+1+n);
    m = unique(t+1,t+n+1)-t-1;
}
int build(int l,int r)
{
    int root = TOT++;
    c[root] = 0;
    if(l != r)
    {
        int mid = (l+r)>>1;
        lson[root] = build(l,mid);
        rson[root] = build(mid+1,r);
    }
    return root;
}
int hashs(int x)
{
    return lower_bound(t+1,t+1+m,x) - t;
}
int update(int root,int pos,int val)
{
    int newroot = TOT++, tmp = newroot;
    c[newroot] = c[root] + val;
    int l = 1, r = m;
    while( l < r)
    {
        int mid = (l+r)>>1;
        if(pos <= mid)
        {
            lson[newroot] = TOT++; rson[newroot] = rson[root];
            newroot = lson[newroot]; root = lson[root];
            r = mid;
        }
        else
        {
            rson[newroot] = TOT++; lson[newroot] = lson[root];
            newroot = rson[newroot]; root = rson[root];
            l = mid+1;
        }
        c[newroot] = c[root] + val;
    }
    return tmp;
}
int query(int left_root,int right_root,int LCA,int k)
{
    int lca_root = T[LCA];
    int pos = hashs(a[LCA]);
    int l = 1, r = m;
    while(l < r)
    {
        int mid = (l+r)>>1;
        int tmp = c[lson[left_root]] + c[lson[right_root]] - 2*c[lson[lca_root]] + (pos >= l && pos <= mid);
        //统计这条路径上权值在离散化数组t中坐标在[l,mid]之间的个数
        //后面减去两倍的他们的lca和后面那个判断 主要是为了如果他们lca被减去两边的话  需要补加一个
        if(tmp >= k)
        {
            left_root = lson[left_root];
            right_root = lson[right_root];
            lca_root = lson[lca_root];
            r = mid;
        }
        else
        {
            k -= tmp;
            left_root = rson[left_root];
            right_root = rson[right_root];
            lca_root = rson[lca_root];
            l = mid + 1;
        }
    }
    return l;
}

/************LCA模板部分************/

int rmq[2*MAXN];//建立RMQ的数组

//ST算法,里面含有初始化init(n)和query(s,t)函数
//点的编号从1开始,1-n.返回最小值的下标
struct ST
{
    int mm[2*MAXN];//mm[i]表示i的最高位,mm[1]=0,mm[2]=1,mm[3]=1,mm[4]=2
    int dp[MAXN*2][20];
    void init(int n)
    {
        mm[0]=-1;
        for(int i=1;i<=n;i++)
        {
            mm[i]=((i&(i-1))==0?mm[i-1]+1:mm[i-1]);
            dp[i][0]=i;
        }
        for(int j=1;j<=mm[n];j++)
          for(int i=1;i+(1<<j)-1<=n;i++)
             dp[i][j]=rmq[dp[i][j-1]]<rmq[dp[i+(1<<(j-1))][j-1]]?dp[i][j-1]:dp[i+(1<<(j-1))][j-1];
    }
    int query(int a,int b)//查询a到b间最小值的下标
    {
        if(a>b)swap(a,b);
        int k=mm[b-a+1];
        return rmq[dp[a][k]]<rmq[dp[b-(1<<k)+1][k]]?dp[a][k]:dp[b-(1<<k)+1][k];
    }
};

//边的结构体定义
struct Node
{
    int to,next;
};

/* ******************************************
LCA转化为RMQ的问题
MAXN为最大结点数。ST的数组 和 F,edge要设置为2*MAXN

F是欧拉序列,rmq是深度序列,P是某点在F中第一次出现的下标
*********************************************/
struct LCA2RMQ
{
    int n;//结点个数
    Node edge[2*MAXN];//树的边,因为是建无向边,所以是两倍
    int tol;//边的计数
    int head[MAXN];//头结点

    bool vis[MAXN];//访问标记
    int F[2*MAXN];//F是欧拉序列,就是DFS遍历的顺序
    int P[MAXN];//某点在F中第一次出现的位置
    int cnt;

    ST st;
    void init(int n)//n为所以点的总个数,可以从0开始,也可以从1开始
    {
        this->n=n;
        tol=0;
        memset(head,-1,sizeof(head));
    }
    void addedge(int a,int b)//加边
    {
        edge[tol].to=b;
        edge[tol].next=head[a];
        head[a]=tol++;
    }

    int query(int a,int b)//传入两个节点,返回他们的LCA编号
    {
        return F[st.query(P[a],P[b])];
    }

    void dfs(int a,int lev)
    {
        vis[a]=true;
        ++cnt;//先加,保证F序列和rmq序列从1开始
        F[cnt]=a;//欧拉序列,编号从1开始,共2*n-1个元素
        rmq[cnt]=lev;//rmq数组是深度序列
        P[a]=cnt;
        for(int i=head[a];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(vis[v])continue;
            dfs(v,lev+1);
            ++cnt;
            F[cnt]=a;
            rmq[cnt]=lev;
        }
    }

    void solve(int root)
    {
        memset(vis,false,sizeof(vis));
        cnt=0;
        dfs(root,0);
        st.init(2*n-1);
    }
}lca;
/***************主席树的建树*****************/
void dfs_build(int u,int pre)
{
    int pos = hashs(a[u]);
    T[u] = update(T[pre],pos,1);
    for(int i = lca.head[u]; i != -1;i = lca.edge[i].next)
    {
        int v =lca.edge[i].to;
        if(v == pre)continue;
        dfs_build(v,u);
    }
}
int main()
{
    while(scanf("%d%d",&n,&q) == 2)
    {
        for(int i = 1;i <= n;i++)
            scanf("%d",&a[i]);
        Init_hashs();
        lca.init(n);
        TOT = 0;
        int u,v;
        for(int i = 1;i < n;i++)
        {
            scanf("%d%d",&u,&v);
            lca.addedge(u,v);
            lca.addedge(v,u);
        }
        lca.solve(1);
        T[n+1] = build(1,m);
        dfs_build(1,n+1);
        int k;
        while(q--)
        {
            scanf("%d%d%d",&u,&v,&k);
            printf("%d\n",t[query(T[u],T[v],lca.query(u,v),k)]);
        }
        return 0;
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
洛谷的SPOJ需要注册一个SPOJ账号并进行绑定才能进行交题。您可以按照以下步骤进行注册: 1. 打开洛谷网站(https://www.luogu.com.cn/)并登录您的洛谷账号。 2. 在网站顶部导航栏中找到“题库”选项,将鼠标悬停在上面,然后选择“SPOJ”。 3. 在SPOJ页面上,您会看到一个提示,要求您注册SPOJ账号并进行绑定。点击提示中的链接,将会跳转到SPOJ注册页面。 4. 在SPOJ注册页面上,按照要求填写您的用户名、密码和邮箱等信息,并完成注册。 5. 注册完成后,返回洛谷网站,再次进入SPOJ页面。您会看到一个输入框,要求您输入刚刚注册的SPOJ用户名。输入用户名后,点击“绑定”按钮即可完成绑定。 现在您已经成功注册并绑定了SPOJ账号,可以开始在洛谷的SPOJ题库上刷题了。祝您顺利完成编程练习!\[1\]\[2\] #### 引用[.reference_title] - *1* *3* [(洛谷入门系列,适合洛谷新用户)洛谷功能全解](https://blog.csdn.net/rrc12345/article/details/122500057)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [luogu p7492 序列](https://blog.csdn.net/zhu_yin233/article/details/122051384)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值