热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

nc多校20219E.Eyjafjalla

nc多校2021-9E.Eyjafjalla链接:E-Eyjafjalla_2021牛客暑期多校训练营9(nowcoder.com)菜狗的人生第一道主席树题目(自主思考、码代码)相

nc多校2021-9E.Eyjafjalla

链接:E-Eyjafjalla_2021牛客暑期多校训练营9 (nowcoder.com)

菜狗的人生第一道主席树题目(自主思考、码代码)

相关知识点:树上倍增主席树(可持久化线段树)

题意:给定n个节点和n-1条边,每个点有一个温度,1号点温度最高,距离1号点越远,温度越低。有q次查询,每次查询询问从x点爆发病毒,病毒的存活温度区间为[l,r],相邻且温度适宜的点会被传染,一共有多少个点会被传染。


分析:



  1. 抽象一下题意:给定一棵以1号点为根,有n个节点的树,树上每个节点有一个温度,满足父节点温度大于其所有子节点、根节点温度最高。

  2. 抽象一下问题:给一个点x和区间[l, r],求包含x在内的且所有点温度在区间内的最大连通块点数。在树上分析可转化为:求以某个点为根的子树中,有多少个点再区间内。

  3. 进一步分析可得,病毒传播可分两个阶段:

    • 从x点向上传播到可存活的最高祖宗节点

    • 然后从该点往下传播到所有可存活的子节点中




做法:



  1. 可以采用树上倍增的方法,在O(logn)的复杂度内求的最高的祖宗节点

  2. 根据树的dfs序建立可持久化线段树(主席树),对每一个询问可做到O(logn)内,查询总复杂度:O(nlogn)


具体步骤:



  1. 读入边,用邻接表存储

  2. 读入每个点的温度,由于最多有100000个点,而温度范围是1~1e9,所以需要将温度先离散化

  3. 从1号节点开始dfs遍历,求出dfs序(用ord[]存储)、所有子树根节点在dfs序中对应的位置(用in[]存储)、所有子树遍历的最后一个儿子节点在dfs序中对应的位置(用out[]存储)、祖宗倍增数组(用fa[][]存储,第二维开到20即可)。

  4. 按照dfs序建立可持久化线段树

  5. 对每个查询,先求最大祖先,然后用可持久化线段树查询点数


树上倍增模板

int fa[N][20]

void dfs(int u, int f)
{
fa[u][0] = f;
for(int i = 1; i <20; i++)
fa[u][i] = fa[fa[u][i - 1]][i - 1];
for(int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if(j != f) dfs(j, u);
}
}
//找x的第k个父亲节点
int find_fa(int i,int k)
{
for(int x = 0; x <= int(log2(k)); x++)
if((1 < i = fa[i][x]; //把i往上提
return i;
}

可持久化线段树分析

以题目给出的样例为例,如下图:

主席树

节点中存三个信息:左儿子、右儿子、区间内的点的数量

按照dfs序,每次加入一个节点构成一个新版本,一共n+1个版本(初始为0)

每次查询加入该节点后,在区间[l, r]中的点数有多少个。由此可得,如果想获得子树的点数,可以用两个版本(子树根节点的前一个版本和子树最后一个节点的版本)的差值求出。

因为:在dfs序中,这两个点之间的所有点就是该子树的所有节点,加入点之前[l, r]的点数减去加入点之后[l, r]的点数就等于加入的点中在[l, r]的点数。

关于开多少节点:线段树本身的4*N个节点+每次更新版本会新开logn个节点,一共(n + 1)个版本,一共开4 * N + 17 * N个节点即可,空间复杂度为O(4n + (n + 1)logn)

解释一下in[]out[]:

以样例为例,可求得:

ord[] = 1 3 2 4
in[] = 1 3 2 4
out[] = 4 4 2 4

以1号点作为根的子树的根在dfs序中的编号为1,该子树最后一个节点在dfs序中的编号为4


AC代码:

#include
#include
#include
#include
using namespace std;
const int N = 1e5+10, INF = 1e9+10;
int n, q;
int h[N], e[N * 2], ne[N * 2], idx;
int tem[N], fa[N][20];
//dfs序,子树根节点对应的dfs下标,对应子树的最后一个儿子节点的dfs下标
int ord[N], in[N], out[N], po;
vector nums;
//主席树节点
struct Node
{
//左右儿子,大小,区间需要通过参数传下去
int l, r;
int si;
}tr[N * 4 + 17 * N];
//不同版本的根节点,点数
int root[N], tot;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
//离散化
int find(int x)
{
return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}
//创建版本0
int build(int l, int r)
{
int p = ++tot;
if(l == r) return p;
int mid = l + r >> 1;
tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);
return p;
}
//上一版本,范围,插入的值
int insert(int p, int l, int r, int x)
{
int q = ++tot;
tr[q] = tr[p];

if(l == r)
{
tr[q].si++;
return q;
}

int mid = l + r >> 1;
if(x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);
else tr[q].r = insert(tr[p].r, mid + 1, r, x);
tr[q].si = tr[tr[q].l].si + tr[tr[q].r].si;
return q;
}
//版本,范围, 温度范围
int query(int p, int l, int r, int min_te, int max_te)
{
if(p == 1 || nums[l] > max_te || nums[r] if(nums[l] >= min_te && nums[r] <= max_te) return tr[p].si;

int mid = l + r >> 1;
return query(tr[p].l, l, mid, min_te, max_te) + query(tr[p].r, mid + 1, r, min_te, max_te);
}
//倍增记录所有点的父节点,并求dfs序
void dfs(int u, int f)
{
ord[++po] = u;
in[u] = po;

fa[u][0] = f;
for(int i = 1; i <20; i++)
fa[u][i] = fa[fa[u][i-1]][i-1];

for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j != f) dfs(j, u);
}
out[u] = po;
}
//倍增找区间内的最大祖先
int find_fa(int u, int max_te)
{
for(int i = 19; ~i; i--)
if(fa[u][i] && tem[fa[u][i]] <= max_te)
u = fa[u][i];
return u;
}
int main()
{
scanf("%d", &n);
memset(h, -1, sizeof h);
for(int i = 0; i {
int u, v;
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
}
for(int i = 1; i <= n; i++)
{
scanf("%d", &tem[i]);
nums.push_back(tem[i]);
}
//离散化
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
//dfs求dfs序和祖宗倍增数组
dfs(1, 0);
//根据dfs序建立主席树
root[0] = build(0, nums.size() - 1);
for(int i = 1; i <= po; i++)
root[i] = insert(root[i - 1], 0, nums.size() - 1, find(tem[ord[i]]));

scanf("%d", &q);
while(q--)
{
int x, l, r;
scanf("%d%d%d", &x, &l, &r);
if(tem[x] > r || tem[x] {
puts("0");
continue;
}
int ro = find_fa(x, r);;
printf("%d\n", query(root[out[ro]], 0, nums.size() - 1, l, r) - query(root[in[ro] - 1], 0, nums.size() - 1, l, r));
}

return 0;
}


推荐阅读
  • 给定一个二维平面上的一些点,通过计算曼哈顿距离,求连接所有点的最小总费用。只有任意两点之间有且仅有一条简单路径时,才认为所有点都已连接。给出了几个示例并给出了对应的输出。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
author-avatar
joyjz
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有