• CF208E Blood Cousins 题解


    一个奇奇怪怪的复杂度很垃圾的线段树合并解法


    通过分析可以发现,要找$x$的$k$辈兄弟,只需要找到$x$的$k$辈祖先,然后查找以该祖先为根的子树中和$x$深度相同的节点个数$-1$即可。也就是说,询问只与深度有关,与具体是哪个节点无关。

    具体怎么实现呢?想到dfs处理,显然在遍历过$x$后会回溯到$x$的$k$辈祖先,因此有一个想法,就是在回溯到$x$的$k$辈祖先时执行查询对应的查询就可以了,这样是一个离线的做法。目前的问题就是,如何能在$x$的$k$辈祖先时执行查询。

    有一个做法就是提前处理出$x$的祖先,然后把询问插入到$x$的$k$辈祖先处,等到回溯到的时候进行查询;另一种就是一种比较奇奇怪怪的做法,利用大根堆维护询问,把祖先节点的深度作为关键字,当遍历到一个节点时,处理深度相同的询问。具体询问用线段树合并就可以处理了。

    还有一个可以优化的地方,如果有祖先节点和查询节点的深度都相同的询问,答案相同,不需要重复查询。

    #include<iostream>
    #include<cstdio>
    #include<vector>
    #include<queue>
    using namespace std;
    const int N=1e5+100;
    struct Seg
    {
        int lson,rson,sum;
        #define lson(i) t[i].lson
        #define rson(i) t[i].rson
        #define sum(i) t[i].sum
    }t[5000000];
    struct Que
    {
        int fa,son,id;//祖先深度,查询节点深度,询问编号
    };
    int n,m,tot,cnt;
    int head[N],ver[N],Next[N];
    int r[N],ans[N];
    bool v[N];
    priority_queue<Que> q;//维护询问
    vector<pair<int,int> > query[N];//存储询问
    bool operator<(const Que &a,const Que &b)
    {
        return a.fa==b.fa?a.son<b.son:a.fa<b.fa;//注意是小于号
    }//大根堆
    void add(int x,int y)
    {
        ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
    }
    int merge(int x,int y)
    {
        if(!x)
            return y;
        if(!y)
            return x;
        sum(x)+=sum(y);
        lson(x)=merge(lson(x),lson(y));
        rson(x)=merge(rson(x),rson(y));
        return x;
    }//线段树合并
    int ask(int &p,int l,int r,int k)
    {
        if(!p)
            p=++cnt;
        if(l==r)
            return sum(p);
        int mid=(l+r)>>1;
        if(k<=mid)
            return ask(lson(p),l,mid,k);
        else
            return ask(rson(p),mid+1,r,k);
    }//查询
    void change(int &p,int l,int r,int k)
    {
        if(!p)
            p=++cnt;
        if(l==r)
        {
            sum(p)++;
            return ;
        }
        int mid=(l+r)>>1;
        if(k<=mid)
            change(lson(p),l,mid,k);
        else
            change(rson(p),mid+1,r,k);
    }//修改
    void solve(int x,int d)
    {
        v[x]=1;//访问数组
        for(int i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            solve(y,d+1);
            r[x]=merge(r[x],r[y]);//子节点合并
        }
        while(q.size() && q.top().fa==d)//处理深度相同的询问
        {
            int y=q.top().son,id=q.top().id;q.pop();
            ans[id]=ask(r[x],1,1e5,y)-1;//查询
            while(q.size() && q.top().fa==d && q.top().son==y)
            {
                ans[q.top().id]=ans[id];
                q.pop();
            }//询问等价不用重复查询
        }
        change(r[x],1,1e5,d);//修改
        for(int i=0;i<query[x].size();i++)
            if(d-query[x][i].first>0)//保证询问合法
            {
                Que now;
                now.fa=d-query[x][i].first;
                now.son=d;
                now.id=query[x][i].second;
                q.push(now);
            }//将询问插入大根堆
    }
    int main()
    {
        scanf("%d",&n);cnt=n;
        for(int i=1,x;i<=n;i++)
        {
            scanf("%d",&x);r[i]=i;
            if(x)
                add(x,i);
        }
        scanf("%d",&m);
        for(int i=1,x,y;i<=m;i++)
        {
            scanf("%d%d",&x,&y);
            query[x].push_back(make_pair(y,i));//插入询问
        }
        for(int i=1;i<=n;i++)
            if(!v[i])
                solve(i,1);
        for(int i=1;i<=m;i++)
            printf("%d ",ans[i]);
        return 0;
    }
  • 相关阅读:
    forward和redirect的区别
    转 jsp中 session的简单用法
    20_学生选课数据库SQL语句练习题1
    _学生选课数据库SQL语句练习题
    输入输出2
    接口提
    输入输出流3
    获取当前时间并显示在网页上
    简单的权限管理
    java关于时间
  • 原文地址:https://www.cnblogs.com/TEoS/p/12977020.html
Copyright © 2020-2023  润新知