• 【CF208E】Blood Cousins


    题目大意:给定一个 N 个点的森林,M 个询问,每次询问对于点 u 来说,有多少个点和 u 有相同的 K 级祖先。

    题解:线段树合并适合处理子树贡献的问题。
    发现要回答这个询问在点 u 处计算很困难,但是在 u 的 k 级祖先处处理询问很简单,即:问对于 v 子树中深度为 k 的节点的个数。因此,采用将询问离线,对于每个点的询问转化到其祖先节点,最后采用线段树合并即可。

    代码如下

    #include <bits/stdc++.h>
    #define mp make_pair
    #define pb push_back
    using namespace std;
    const int maxn=1e5+10;
    
    int n,m,ans[maxn];
    vector<int> G[maxn];
    int f[maxn][21],dep[maxn];
    vector<pair<int,int>> q[maxn]; // dep, id
    
    void pre(int u,int fa){
    	dep[u]=dep[fa]+1,f[u][0]=fa;
    	for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];
    	for(auto v:G[u])pre(v,u);
    }
    int LA(int x,int k){
    	int t=dep[x]-k;
    	for(int i=20;~i;i--)if(dep[f[x][i]]>=t)x=f[x][i];
    	return x;
    }
    
    struct node{
    	#define ls(o) t[o].lc
    	#define rs(o) t[o].rc
    	int lc,rc,sz;
    }t[maxn*20];
    int tot,root[maxn];
    inline void pushup(int o){
    	t[o].sz=t[ls(o)].sz+t[rs(o)].sz;
    }
    void insert(int &o,int l,int r,int pos){
    	if(!o)o=++tot;
    	if(l==r){++t[o].sz;return;}
    	int mid=l+r>>1;
    	if(pos<=mid)insert(ls(o),l,mid,pos);
    	else insert(rs(o),mid+1,r,pos);
    	pushup(o);
    }
    int merge(int x,int y,int l,int r){
    	if(!x||!y)return x+y;
    	if(l==r){t[x].sz+=t[y].sz;return x;}
    	int mid=l+r>>1;
    	ls(x)=merge(ls(x),ls(y),l,mid);
    	rs(x)=merge(rs(x),rs(y),mid+1,r);
    	return pushup(x),x;
    }
    int query(int o,int l,int r,int pos){
    	if(!o)return 0;
    	if(l==r)return t[o].sz;
    	int mid=l+r>>1;
    	if(pos<=mid)return query(ls(o),l,mid,pos);
    	else return query(rs(o),mid+1,r,pos);
    }
    
    void dfs(int u){
    	for(auto v:G[u]){
    		dfs(v);
    		root[u]=merge(root[u],root[v],1,n);
    	}
    	for(auto p:q[u]){
    		int d=p.first,id=p.second;
    		ans[id]=query(root[u],1,n,d)-1;
    	}
    	insert(root[u],1,n,dep[u]);
    }
    void read_and_parse(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++){
    		scanf("%d",&f[i][0]);
    		if(!f[i][0])continue;
    		G[f[i][0]].pb(i);
    	}
    	for(int i=1;i<=n;i++)if(!f[i][0])pre(i,0);
    	scanf("%d",&m);
    	for(int i=1;i<=m;i++){
    		int v,p;
    		scanf("%d%d",&v,&p);
    		int u=LA(v,p);
    		if(!u)continue;
    		q[u].pb(mp(dep[v],i));
    	}
    }
    void solve(){
    	for(int i=1;i<=n;i++)if(!f[i][0])dfs(i);
    	for(int i=1;i<=m;i++)printf("%d ",ans[i]);
    }
    int main(){
    	read_and_parse();
    	solve();
    	return 0;
    } 
    
  • 相关阅读:
    Windows7如何让快速启动栏的资源管理器默认打开我的电脑(计算机)
    [转]C# Invoke和BeginInvoke的区别
    [转]RAID0、RAID1、RAID0+1、RAID5原理介绍
    【引用】Dashboard设计思路
    [Resolution]Your search cannot be completed because this site is not assigned to an indexer
    A Brief Introduction to Boolean Algebra(布尔代数简介)
    [转]从头开始安装Eclipse和VE
    [转]领导者如何问更好的问题
    如何使用花生壳(www.oray.com)实现动态域名映射(DDNS)
    如何手动删除Windows服务
  • 原文地址:https://www.cnblogs.com/wzj-xhjbk/p/10985953.html
Copyright © 2020-2023  润新知