acdream 1018 Hotel(函数式线段树、最近公共祖先)

题目链接:http://www.acdream.net/problem.php?id=1018

题意:给出一棵树,每个节点有一个值w。若干询问,每个询问s,t,a,b,k,询问从s节点走到t节点权值在[a,b]之间的第k个节点。

思路:

(1)首先把从根出发到每个点的权值分布用函数式线段树全部记录,则通过函数式线段树的减法操作以及计算两个点LCA就可以得到任意两个点路径上权值a到b的点个数。

(2)记录每个点的2次幂祖先,可以计算两点LCA;

(3)分别计算s到LCA和LCA 到t中权值在[a,b]的点个数left和right,如果left+right小于k,则输出-1;如果left不少于k,则答案点肯定在s到LCA上,否则在LCA到t上。

#include <iostream>

#include <cstdio>

#include <vector>

#include <string.h>

using namespace std;





struct node

{

    int a,b,L,R,s,mid;

};



const int MAX=100005;

node p[MAX*20];

int root[MAX],tot;

int f[MAX][25],n,m,w[MAX],dep[MAX];

vector<int> g[MAX];



int build(int a,int b)

{

    int k=++tot;

    p[k].a=a;

    p[k].b=b;

    p[k].s=0;

    p[k].mid=(a+b)>>1;

    if(a==b) return k;

    p[k].L=build(a,p[k].mid);

    p[k].R=build(p[k].mid+1,b);

    return k;

}



int change(int c,int s)

{

    int k=++tot;

    p[k]=p[c];

    p[k].s++;

    if(p[k].a==p[k].b) return k;

    if(s<=p[k].mid) p[k].L=change(p[c].L,s);

    else p[k].R=change(p[c].R,s);

    return k;

}



void DFS(int u,int pre,int depth)

{

    f[u][0]=pre;

    dep[u]=depth;

    root[u]=change(root[pre],w[u]);

    int i,v;

    for(i=0;i<g[u].size();i++)

    {

        v=g[u][i];

        if(v==pre) continue;

        DFS(v,u,depth+1);

    }

}



int getLCA(int a,int b)

{

    int i,temp;

    if(dep[a]>dep[b])

    {

        temp=a;

        a=b;

        b=temp;

    }

    if(dep[a]<dep[b])

    {

        temp=dep[b]-dep[a];

        for(i=0;i<20;i++) if(temp&(1<<i)) b=f[b][i];

    }

    if(a==b) return a;

    for(i=19;i>=0;i--)

    {

        if(f[a][i]!=f[b][i]&&f[a][i]&&f[b][i])

        {

            a=f[a][i];

            b=f[b][i];

        }

    }

    a=f[a][0];

    return a;

}



int cal(int x,int y,int a,int b)

{

    if(p[x].b<a||p[x].a>b) return 0;

    if(a<=p[x].a&&p[x].b<=b) return p[x].s-p[y].s;

    return cal(p[x].L,p[y].L,a,b)+cal(p[x].R,p[y].R,a,b);

}



int getCnt(int x,int y,int a,int b)

{

    return cal(root[x],root[f[y][0]],a,b);

}



int search(int x,int y,int k,int a,int b,int t)

{

    if(x==y) return x;

    if(y==f[x][0])

    {

        if(k==1&&a<=w[x]&&w[x]<=b) return x;

        return y;

    }

    if(f[x][t]==0||dep[f[x][t]]<=dep[y])

    {

        return search(x,y,k,a,b,t-1);

    }

    int cnt=getCnt(x,f[x][t],a,b);

    if(k<=cnt) return search(x,f[x][t],k,a,b,t-1);

    else return search(f[f[x][t]][0],y,k-cnt,a,b,t-1);

}



void deal()

{

    int i,s,t,a,b,k,temp,lca;

    int left,right;

    while(m--)

    {

        scanf("%d%d%d%d%d",&s,&t,&a,&b,&k);

        lca=getLCA(s,t);

        left=getCnt(s,lca,a,b);

        right=getCnt(t,lca,a,b);

        temp=(a<=w[lca]&&w[lca]<=b);

        if(left+right-temp<k)

        {

            puts("-1");

            continue;

        }

        if(left>=k) i=search(s,lca,k,a,b,19);

        else i=search(t,lca,left+right-temp-k+1,a,b,19);

        printf("%d\n",i);

    }

}



int main()

{

    while(scanf("%d%d",&n,&m)!=-1)

    {

        int i,j,u,v;

        for(i=1;i<=n;i++) g[i].clear();

        memset(f,0,sizeof(f));

        for(i=1;i<=n;i++) scanf("%d",w+i);

        for(i=1;i<n;i++)

        {

            scanf("%d%d",&u,&v);

            g[u].push_back(v);

            g[v].push_back(u);

        }

        tot=0;

        root[0]=build(0,100000);

        DFS(1,0,0);

        for(i=1;i<20;i++) for(j=1;j<=n;j++) f[j][i]=f[f[j][i-1]][i-1];

        deal();

    }

    return 0;

}

  

 

你可能感兴趣的:(线段树)