首页 > 其他分享 >ABC202E Count Descendants

ABC202E Count Descendants

时间:2023-10-15 17:14:47浏览次数:33  
标签:Count ABC202E Descendants int 线段 合并 edge mathcal 复杂度

ABC202E Count Descendants

线段树合并模板题。


每次询问就是给定有序数对 \((u,d)\),求有根树 \(T\) 上,点 \(u\) 的子树内有多少点 \(v\),使得 \(v\) 的深度恰好等于 \(d+1\)。定义根节点深度为 \(1\)。

考虑对每一个点开一个长度为 \(n\) (因为 \(T\) 的最大深度为 \(n\))的数组 \(a_u\),\(a_{u,i}\) 表示 \(u\) 的子树内深度为 \(i\) 的点有多少,同时记录每个点的深度 \(depth\)。

每递归到叶节点 \(v\),就将 \(a_{v,i}\) 加上 \(1\)。

回溯时,将 \(a_v\) 暴力合并到 \(a_u\),即 \(\forall i \in [1,n],a_{u,i} \leftarrow a_{u,i}+a_{v,i}\),这一步的时间复杂度为 \(\mathcal{O}(n)\)。单次查询可以做到 \(\mathcal{O}(1)\)。于是该做法的时间、空间复杂度均为 \(\mathcal{O}(n^2)\)。


显然,时间复杂度的瓶颈在于合并操作,考虑如何优化这一步。

考虑线段树合并,对每一个 \(u\) 开一棵动态开点权值线段树,每次回溯时将 \(v\) 对应的线段树与 \(u\) 合并即可,单次时间复杂度 \(\mathcal{O}( \log n)\)。

注意不能将 \(v\) 对应的线段树直接合并到 \(u\),因为这样会导致 \(u\) 对应线段树的信息丢失,所以应该先新建一个节点 \(o\),再将 \(u\) 和 \(v\) 的节点信息合并至 \(o\)。当然离线询问也可以解决这个问题。

由于每次会多开 \(\mathcal{O}(\log n)\) 个点,所以线段树的数组大小要在原来的基础上再多开一倍。总复杂度 \(\mathcal{O}(n \log n)\)。

树上启发式合并 也可以解决本题,时间复杂度仍然是 \(\mathcal{O}(n \log n)\)。

下面是线段树合并的代码:

#include<bits/stdc++.h>
using namespace std;
bool Mbegin;
void File_Work(){
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
}
namespace Fast_In_Out{
	char buf[1<<21],*P1=buf,*P2=buf;
	#define gc (P1==P2&&(P2=(P1=buf)+fread(buf,1,1<<21,stdin),P1==P2)?EOF:*P1++)
	int read(){
		int f=1,x=0;
		char c=gc;
		while(c<'0'||c>'9'){
			if(c=='-')
			    f=-1;
			c=gc;
		}
		while(c>='0'&&c<='9'){
			x=x*10+c-'0';
			c=gc;
		}
		return f*x;
	}
	void write(int x){
		if(x<0)
			x=-x,putchar('-');
		if(x>9)
			write(x/10);
		putchar(x%10+'0');
	}
	#undef gc
}
using namespace Fast_In_Out;
const int N=2e5+8;
int n,m;
struct Graph{
	int head[N],edge_tot=1,to[N],next[N];
	void add_edge(int u,int v){
		edge_tot++;
		to[edge_tot]=v;
		next[edge_tot]=head[u];
		head[u]=edge_tot;
	}
}Tree;
int depth[N];
int rt[N];
struct Segemnt_Tree{
	int ocnt,ls[N<<6],rs[N<<6],sum[N<<6];
	void push_up(int o){
		sum[o]=sum[ls[o]]+sum[rs[o]];
	}
	void insert(int &o,int l,int r,int pos){
		o=++ocnt;
		if(l==r){
			sum[o]++;
			return;
		}
		int mid=(l+r)/2;
		if(pos<=mid)
			insert(ls[o],l,mid,pos);
		else
			insert(rs[o],mid+1,r,pos);
		push_up(o);
	}
	int query(int o,int l,int r,int pos){
		if(o==0)
			return 0;
		if(l==r)
			return sum[o];
		int mid=(l+r)/2;
		if(pos<=mid)
			return query(ls[o],l,mid,pos);
		else
			return query(rs[o],mid+1,r,pos);
	}
	int merge(int u,int v){
		if(u==0||v==0)
			return u+v;
		int o=++ocnt;
		ls[o]=merge(ls[u],ls[v]);
		rs[o]=merge(rs[u],rs[v]);
		sum[o]=sum[u]+sum[v];
		return o;
	}
}smt;
void dfs(int u,int father){
	depth[u]=depth[father]+1;
	smt.insert(rt[u],1,n,depth[u]);
	for(int i=Tree.head[u];i;i=Tree.next[i]){
		int v=Tree.to[i];
		dfs(v,u);
		rt[u]=smt.merge(rt[u],rt[v]);
	}
}
bool Mend;
int main(){
//	File_Work();
	fprintf(stderr,"%.3lf MB\n\n\n",(&Mbegin-&Mend)/1048576.0);
	n=read();
	for(int i=2;i<=n;i++){
		int father=read();
		Tree.add_edge(father,i);
	}
	dfs(1,0);
	m=read();
	while(m--){
		int a=read(),b=read()+1;
		write(smt.query(rt[a],1,n,b)),putchar('\n');
	}
	fprintf(stderr,"\n\n\n%.0lf ms",1e3*clock()/CLOCKS_PER_SEC);
	return 0;
}

线段树合并的题,通常可以先想 \(\mathcal{O}(n^2)\) 做法,再进行优化。

思想几乎一样的题:CF208E Blood Cousins

另一道线段树合并模板题:CF600E Lomsat gelral

标签:Count,ABC202E,Descendants,int,线段,合并,edge,mathcal,复杂度
From: https://www.cnblogs.com/NatoriBlog/p/17765813.html

相关文章

  • [USACO17JAN] Promotion Counting P 题解
    [USACO17JAN]PromotionCountingP题解前言好久没写题解了,今天趁我心情好赶紧水一篇。思路首先拿到这题,关键词检索:子树,比\(p_i\)大的,树状数组!现在考虑如何去掉其他子树的贡献……emm,我直接在算贡献的时候去掉其他子树的贡献不就好了!当然,暴力去贡献复杂度肯定爆炸,这里考虑......
  • 执行wordcount报错及解决
    今天在执行wordcount词频统计时报错执行语句为hadoopjarshare/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jarwordcountwcinputwcoutput报错如下 这表示指定的输入路径hdfs://hadoop102:8020/user/atguigu/wcinput不存在然后我打开hadoop可视化网页一看确实......
  • 泛型算法(find、count、sort、fill、unique、copy、lambda、迭代器)
    一、概述algorithm中,还有一些数值泛型算法定义在头文件numeric中。算法永远不会执行容器的操作)。1、find和count:#include<iostream>#include<algorithm>#include<numeric>#include<vector>#include<list>usingnamespacestd;usingv_int=vector<int>;usingv_s......
  • CountDownLatch、CyclicBarrier、Semaphore面试讲解
    @TOC<hrstyle="border:solid;width:100px;height:1px;"color=#000000size=1">这三个也是面试常问的,作为线程通信的方法1.CountDownLatch(CDL)主要是用于一个线程等待其他完成后才继续执行。主要方法:await()、countDown()CountDownLatchcdl=newCountDownLatch(2);//第一......
  • count_ga_5.py
      #!/usr/bin/python3'''作用:统计港澳车的识别率,分别输出港牌和澳牌识别失败的港澳车的二次识别车牌、筛选过的时间和图片url的csv文件'''importosimportsysimportreimportpymysqlimporttimeimportdatetimeimportloggingimportpandasaspdimportre......
  • 实践一下前几天的wordCount案例
    1、自己准备一个数据量比较小的txt文件然后将其上传到虚拟机本地:之后上传到hdfs里面:2、编写代码1、引入相关依赖<dependencies><!--https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common--><dependency><groupId>org.a......
  • JUC工具类CountDownLatch、CyclicBarrier、Semaphore介绍
    CountDownLatch:它是一种同步工具,用于控制一个或多个线程等待其他线程完成操作后再继续执行。它的作用是让某个线程等待一组操作执行完成,再继续执行自己的任务。CountDownLatch内部有一个计数器,当计数器的值为0时,等待的线程将会被唤醒。通过CountDownLatch的构造函数可以指定计......
  • MapReduce学习二之WordCount案例
    一、案例概述1、第一步--变成偏移量的K1,V1(这一步不需要我们自己写)2、进入Map阶段输出新的<K2,V2>的键值对;3、Shuffle阶段分区、排序、规约、分组输出新的键值对:4、Reduce阶段转换为<K3,V3>的新的形式的键值对;利用TextOutputFormat的类实现结果的输出;二、具体实践1......
  • Ubuntu 安装谷歌浏览器报错解决:Errors were encountered while processing
    Ubuntu安装谷歌浏览器报错解决parallels@ubuntu-linux-22-04-02-desktop:~/snap/firefox/common/Downloads$sudodpkg-igoogle-chrome-stable_current_amd64.deb[sudo]passwordforparallels:dpkg:errorprocessingarchivegoogle-chrome-stable_current_amd64.deb(......
  • G. Counting Graphs
    G.CountingGraphs题意:添加几条线段,使得图仍保持原先的最小生成树通过画图我们发现,要添加u->v的线段,线段必须大于u->v的路径内的最大值,不然会破坏原先的最小生成树。那么该怎么维护路径内的最大值呢?方法:1.我们对边的大小进行排序,这样当前边一定大于等于之前的边,只要对当前边......