spoj Count on a tree【主席树+在线LCA】

10628. Count on a tree

Problem code: COT

 

You are given a tree with N nodes.The tree nodes are numbered from 1 to N.Each node has an integer weight.

We will ask you to perform the following operation:

  • u v k : ask for the kth minimum weight on the path from node u to node v

 

Input

In the first line there are two integers N and M.(N,M<=100000)

In the second line there are N integers.The ith integer denotes the weight of the ith node.

In the next N-1 lines,each line contains two integers u v,which describes an edge (u,v).

In the next M lines,each line contains three integers u v k,which means an operation asking for the kth minimum weight on the path from node u to node v.

Output

For each operation,print its result.

Example

Input:
8 5
8 5
105 2 9 3 8 5 7 7
1 2        
1 3
1 4
3 5
3 6
3 7
4 8
2 5 1
2 5 2
2 5 3
2 5 4
7 8 2 
Output:
2
8
9
105
7 

一天只做了这么一个题,好歹做出来了6666,(其实是照着终于改对了,逃,所以以后还得多做数据结构的题,对于debug能力很有提高,毕竟动辄200行

说题意:真是嗷嗷简单易读的题,树上两点间的所有数找最小值,说思路也是嗷嗷简单,就是在树上建主席树嘛。第一次做树套树的题,被代码量惊呆了,我把一个二分while改成递归还180行呢,上午基本上看懂了邝斌代码的整体思路以及几个函数,本公举今天心情好,把代码按着自己之前用的模板敲了一遍,又试着拼接,现在是真懂了主席树的那些函数以及在线LCA的算法。要不说出来混总是要还的,前几天刷RMQ的时候把LCA的遗留问题给忘了,这次的题明显不应该用离线的,参考了这篇博客:点击打开链接,看懂了代码,自己写的也是用的他的模板,真心挺简单的。

言归正传,主席树是求区间第k小的值,放到这个题里面,除了query函数需要改之外,其他直接照搬,用的还是孙大神的模板,结构体左右结点的写法看起来很清晰,坑也就坑在query函数里面了,其实我最开始就怀疑是不是这里的问题,把所有能打印的数组打出来看了,都没问题,才仔细看了query。公共祖先的Rt是不应该每次递归都求的!说不通了啊

LCA没问题,但是建树的函数需要用心想,以后,也就是这两周,我要努力刷树套树,想是一码事,根本写不出啊!

/*********
spoj Count on a tree
2016.1.25
152576	2330ms
C++ (g++ 4.9.2)
*********/
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;

#define maxn 200020
///=============主席树部分开始
struct Node
{
    int ls,rs,cnt;
}tr[maxn*40];
int cur,m,n,q,rt[maxn],b[maxn],sortb[maxn];
void tree_init()
{
    cur=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&b[i]);
        sortb[i]=b[i];
    }
    sort(sortb+1,sortb+1+n);
    m=1;
    for(int i=2;i<=n;i++)
    {
        if(sortb[i]!=sortb[m]) sortb[++m]=sortb[i];
    }
}
int build(int l,int r)
{
    int k=cur++;
    if(l==r)
    {
        tr[k].cnt=0;
        return k;
    }
    int mid=(l+r)>>1;
    tr[k].ls=build(l,mid);
    tr[k].rs=build(mid+1,r);
    tr[k].cnt=0;
    return k;
}
int update(int o,int l,int r,int pos,int val)
{
    int k=cur++;
    tr[k]=tr[o];
    if(l==pos&&r==pos)
    {
        tr[k].cnt+=val;
        return k;
    }
    int mid=(l+r)>>1;
    if(pos<=mid) tr[k].ls=update(tr[o].ls,l,mid,pos,val);
    else tr[k].rs=update(tr[o].rs,mid+1,r,pos,val);
    tr[k].cnt=tr[tr[k].ls].cnt+tr[tr[k].rs].cnt;
    return k;
}
///==============主席树部分结束
///==============在线LCA开始
int dp[maxn*2][25],num;
bool vis[maxn];
struct Edge
{
    int u,v,next;///已知的是点权,不是边权!
}e[maxn<<1];
int tot,head[maxn];
void lca_init()
{
    tot=0;
    num=0;
    memset(head,-1,sizeof(head));
    memset(vis,false,sizeof(vis));
}
inline void add(int u,int v,int &k)
{
    e[k].u=u;e[k].v=v;e[k].next=head[u];head[u]=k++;
    e[k].u=v;e[k].v=u;e[k].next=head[v];head[v]=k++;
}
int ver[maxn*2],R[maxn*2],first[maxn];///节点编号、深度、(前两个下标是遍历次序)点位置
//因为是深搜的序列,欧拉序列长度=2*n-1,我居然才知道!
void dfs(int u,int pre,int dep)//不可以用vis数组判环,也可以用pre参数
{
    ver[++tot]=u;
    first[u]=tot;
    R[tot]=dep;
    for(int k=head[u];k!=-1;k=e[k].next)
    {
        int v=e[k].v;
        if(v==pre) continue;
          ///  dir[v]=dir[u]+w;因为没有边权值,所以深度=距离 dir[]数组没有用
        dfs(v,u,dep+1);
        ver[++tot]=u;
        R[tot]=dep;
    }
}
void ST(int n)
{
    for(int i=1;i<=n;i++) dp[i][0]=i;
    for(int j=1;(1<<j)<=n;j++)
        for(int i=1;i+(1<<j)-1<=n;i++)
    {
       // if(i + (1 << (j - 1)) > n) continue;
        int a=dp[i][j-1],b=dp[i+(1<<(j-1))][j-1];
        dp[i][j]=R[a]<R[b]?a:b;
    }
}
int RMQ(int l,int r)
{
    int k=0;
    while((1<<(k+1))<=r-l+1) k++;
    int a=dp[l][k],b=dp[r-(1<<k)+1][k];
    return R[a]<=R[b]?a:b;//?
}
int LCA(int u,int v)
{
    int x=first[u],y=first[v];
    if(x>y) swap(x,y);
    int res=RMQ(x,y);
    return ver[res];
}
///=============LCA结束
///=============综合部分:树套树+查询
void dfs_build(int u,int pre)
{
     int pos=lower_bound(sortb+1,sortb+1+m,b[u])-sortb;
     rt[u]=update(rt[pre],1,m,pos,1);
     for(int k=head[u];k!=-1;k=e[k].next)
     {
         int v=e[k].v;
         if(v==pre) continue;
         dfs_build(v,u);
     }
}
int query(int l,int r,int o,int v,int Lca,int k)
{
   /* if(l==r) return l;
    int mid=(l+r)>>1;
    int
    int lca=rt[Lca];

    if(k<=tmp) return query(l,mid,tr[o].ls,tr[v].ls,tr[lca].ls,k);
    else return query(mid+1,r,tr[o].rs,tr[v].rs,tr[lca].rs,k-tmp);*/
    int lca_root=rt[Lca];
    int pos=lower_bound(sortb+1,sortb+1+m,b[Lca])-sortb;
    while(l<r)
    {
        int mid=(l+r)>>1;
        int tmp=tr[tr[v].ls].cnt+tr[tr[o].ls].cnt-2*tr[tr[lca_root].ls].cnt+(pos>=l&&pos<=mid);
        if(tmp>=k)
        {
            o=tr[o].ls;v=tr[v].ls;lca_root=tr[lca_root].ls;
            r=mid;
        }
        else
        {
            k-=tmp;
            o=tr[o].rs;v=tr[v].rs;lca_root=tr[lca_root].rs;
            l=mid+1;
        }
    }
    return l;
}
///============综合部分结束
int main()
{
    //freopen("cin.txt","r",stdin);
   // freopen("out.txt","w",stdout);
    while(scanf("%d%d",&n,&q)==2)
    {
        tree_init();
        lca_init();
        for(int i=1;i<n;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);//居然是忘了读入数据==
            add(x,y,num);
        }
        dfs(1,1,0);///就当是1是根节点 以谁为根节点建树都一样,树枝都可以掰过去
        ST(n*2-1);
        rt[0]=build(1,m);
        dfs_build(1,0);
        int u,v,kth;
        while(q--)
        {
            scanf("%d%d%d",&u,&v,&kth);
            int tmp=LCA(u,v);
            //printf("u=%d v=%d lca=%d   ",u,v,tmp);
            printf("%d\n",sortb[query(1,m,rt[u],rt[v],tmp,kth)]);
        }
       /* for(int i=1;i<=n;i++) printf("i=%d,rt=%d,first=%d\n",i,rt[i],first[i]);
        for(int i=1;i<=m;i++) printf("i=%d,sortb=%d\n",i,sortb[i]);
        for(int i=1;i<=2*n-1;i++) printf("i=%d,ver=%d,R=%d\n",i,ver[i],R[i]);
        for(int j=1;(1<<j)<=2*n-1;j++)
        for(int i=1;i+(1<<j)-1<=2*n-1;i++)
    {
       // if(i + (1 << (j - 1)) > n) continue;
        printf("i=%d,j=%d,dp=%d\n",i,j,dp[i][j]);
    }
       /* puts("7,4");
        printf("%d\n",sortb[query(1,m,rt[7],rt[4],LCA(7,4),1)]);
        printf("%d\n",sortb[query(1,m,rt[7],rt[4],LCA(7,4),2)]);
        printf("%d\n",sortb[query(1,m,rt[7],rt[4],LCA(7,4),3)]);
        printf("%d\n",sortb[query(1,m,rt[7],rt[4],LCA(7,4),4)]);
        puts("5,8");
        printf("%d\n",sortb[query(1,m,rt[5],rt[8],LCA(5,8),1)]);
        printf("%d\n",sortb[query(1,m,rt[5],rt[8],LCA(5,8),2)]);
        printf("%d\n",sortb[query(1,m,rt[5],rt[8],LCA(5,8),3)]);
        printf("%d\n",sortb[query(1,m,rt[5],rt[8],LCA(5,8),4)]);
        printf("%d\n",sortb[query(1,m,rt[5],rt[8],LCA(5,8),5)]);
        printf("%d\n",sortb[query(1,m,rt[5],rt[8],LCA(5,8),6)]);
        puts("");
        printf("%d\n",sortb[query(1,m,rt[6],rt[8],LCA(6,8),1)]);
        printf("%d\n",sortb[query(1,m,rt[6],rt[8],LCA(6,8),2)]);
        printf("%d\n",sortb[query(1,m,rt[6],rt[8],LCA(6,8),3)]);
        printf("%d\n",sortb[query(1,m,rt[6],rt[8],LCA(6,8),4)]);
        printf("%d\n",sortb[query(1,m,rt[6],rt[8],LCA(6,8),5)]);
        printf("%d\n",sortb[query(1,m,rt[6],rt[8],LCA(6,8),6)]);
        puts("");
        puts("2,8");
        printf("%d\n",sortb[query(1,m,rt[2],rt[8],LCA(2,8),1)]);
        printf("%d\n",sortb[query(1,m,rt[2],rt[8],LCA(2,8),2)]);
        printf("%d\n",sortb[query(1,m,rt[2],rt[8],LCA(2,8),3)]);
        printf("%d\n",sortb[query(1,m,rt[2],rt[8],LCA(2,8),4)]);
        puts("1,8");
        printf("%d\n",sortb[query(1,m,rt[1],rt[8],LCA(1,8),1)]);
        printf("%d\n",sortb[query(1,m,rt[1],rt[8],LCA(1,8),2)]);
        printf("%d\n",sortb[query(1,m,rt[1],rt[8],LCA(1,8),3)]);
        puts("");*/
    }
    return 0;
}

祝愿自己今天晚上做梦不要梦到比赛了T^T

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值