这应该属于一个比较麻烦的数据结构处理树上问题.

题目大意

给出一颗根节点编号为 \(1\) 的树,对于一个节点修改时在它的子树中对于深度奇偶性相同的节点加上这个权值,不同则减去这个值,单点查询.

分析

先给出这个问题的弱化版:

给出一颗根节点编号为 \(1\) 的树,对于一个节点修改时在它的子树中节点也加上这个权值,单点查询.

对于上面这个问题就好处理很多了,DFS序有一个性质,又称括号定理,在树中这个就会体现在再DFS序中以 \(a\) 为根节点的子树中的所有节点都在 \(a\) 的后面,且连续,于是对于一棵树的修改在DFS序中的就变成了区间修改,那么久可以拿出一些数据结构来维护这个东西.(例如线段树,树状数组,平衡树...)

下面回到这个问题,可以发现对于深度不同的点的修改可能是不同的,只有两种修改(\(+val\) 和 \(-val\))而且这两个修改于这个点奇偶性有关,那就有一个鼻尖暴力的想法,既然和奇偶性有关,那么久维护两颗线段树,分别维护深度为奇数的点和深度为偶数的点的值,那就变会了第一个问题.

细节处理

  1. 记得清空 \(Lazy\) 标记和下传 \(Lazy\) 标记.
  2. 在连边的时候需要连双向边,并且需要注意边的数组需要开两倍(这应该算是常识吧).
  3. 需要处理出每个点为根节点的树中深度为奇数的点的个数和深度为偶数的点的个数,需要用来计算修改的区间的开始和结束位置.
  4. 可以发现每次修改在两颗树都需要修改,对于加上 \(val\) 的部分很好计算,但是对于和它深度的奇偶性不同的节点的修改就比较麻烦了,所以在DFS的时候最好记录一下每个节点的第一个子节点是谁,即谁是在DFS序(不按奇偶分开)中的下一个节点,在修改的时候只要在另一颗线段树中以它为左端点,长度为这整颗子树中的所有深度的奇偶性与根节点不同的节点个数来修改.
  5. 记得需要特判一下这颗树只有一个节点的情况,这样会有一颗线段树中的大小为 \(0\),但是如果在建树的时候左端点从 \(1\) 开始,右端点为 \(0\),会无限递归,然后MLE.(亲身经历)

代码实现

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
const int maxN=3e5+7;
int N,M;
int val[maxN];
//链式前向星,基础知识,不解释
struct Edge
{
int to,next;
}edge[maxN*2];
int edge_head[maxN];
int edge_cnt=0;
#define FOR(now) for(int i_edge=edge_head[now];i_edge;i_edge=edge[i_edge].next)
#define TO edge[i_edge].to
void AddEdge(int form,int to)//加边
{
edge[++edge_cnt].to=to;
edge[edge_cnt].next=edge_head[form];
edge_head[form]=edge_cnt;
}
//下文中0表示偶数,1表示奇数,根节点为的深度为1
int deep[maxN];//每个节点的深度
int first_son[maxN];//记录每个节点的第一个子节点
int place[maxN];//记录每格节点在按奇偶分开后的位置,因为之后存在奇偶中的一个,所以只要不需要分别
int dfs[2][maxN];//记录奇偶的两个DFS序
int dfs_cnt[2]={0,0};//记录DFS序长度
int size[2][maxN];//记录每个节点为根节点的子树中深度为奇数的节点个数和深度为偶数的节点个数
bool visit[maxN];//判断这个点是否被访问过
#define TEAM (deep[now]&1)//方便使用
void DFS(int now=1)
{
visit[now]=1;//记录访问过这个节点
dfs[TEAM][++dfs_cnt[TEAM]]=now;//将这个点放入它改进入的DFS序中
place[now]=dfs_cnt[TEAM];//记录出现的位置
size[TEAM][now]=1;//自己和自己的深度的奇偶性自然相同
FOR(now)
{
if(!visit[TO])//如果没有访问过才访问
{
if(!first_son[now])//如果这个是他的第一个子节点
{
first_son[now]=TO;
}
deep[TO]=deep[now]+1;//子节点的深度为父节点的深度+1
DFS(TO);//继续DFS
//一下为记录以当前节点为根节点的子树中深度为奇和偶的节点各自的出现次数
size[TEAM^1][now]+=size[TEAM^1][TO];
size[TEAM][now]+=size[TEAM][TO];
}
}
}
#undef TEAM
#undef FOR
#undef TO
struct LazyTag//Lazy标记
{
int add;
void CleanLazyTag()//清空标记
{
add=0;
}
}ForMake;
LazyTag MakeLazyTag(int add)//制作一个标记
{
ForMake.add=add;
return ForMake;
}
//线段树不是本文终于内容,不细讲
struct SegmentTree//线段树
{
int num;
LazyTag tag;
}sgt[2][maxN*4];//奇偶两颗线段树
#define LSON (now<<1)
#define RSON (now<<1|1)
#define MIDDLE ((left+right)>>1)
#define LEFT LSON,left,MIDDLE
#define RIGHT RSON,MIDDLE+1,right
#define NOW team,now_left,now_right,add
//想每个函数中的第一个参数team表示修改的是哪一颗线段树
void Build(int team,int now,int left,int right)//建树
{
if(left==right)
{
sgt[team][now].num=val[dfs[team][left]];
return;
}
Build(team,LEFT);
Build(team,RIGHT);
}
void Down(int team,LazyTag tag,int now,int left,int right)//修改部分
{
sgt[team][now].tag.add+=tag.add;
if(left==right)//只有叶节点需要修改值
{
sgt[team][now].num+=tag.add;
}
}
void PushDown(int team,int now,int left,int right)//下传懒标记
{
if(sgt[team][now].tag.add)
{
Down(team,sgt[team][now].tag,LEFT);
Down(team,sgt[team][now].tag,RIGHT);
sgt[team][now].tag.CleanLazyTag();
}
}
void Updata(int team,int now_left,int now_right,int add,int now,int left,int right)//修改部分
{
if(now_right<left||right<now_left)
{
return;
}
if(now_left<=left&&right<=now_right)
{
Down(team,MakeLazyTag(add),now,left,right);
return;
}
PushDown(team,now,left,right);
Updata(NOW,LEFT);
Updata(NOW,RIGHT);
}
int Query(int team,int place,int now,int left,int right)//查询
{
if(right<place||place<left)
{
return 0;
}
if(left==right)
{
return sgt[team][now].num;
}
PushDown(team,now,left,right);
return Query(team,place,LEFT)+Query(team,place,RIGHT);
}
#undef LSON
#undef RSON
#undef MIDDLE
#undef LEFT
#undef RIGHT
#undef NOW
#define TEAM (deep[u]&1)
int main()
{
scanf("%d%d",&N,&M);
REP(i,1,N)
{
scanf("%d",&val[i]);
}
int father,son;
REP(i,1,N-1)
{
scanf("%d%d",&father,&son);
AddEdge(father,son);//连边
AddEdge(son,father);
}
deep[1]=1;
DFS();//BFS
//注意建树前需要判断树中是否有节点
if(dfs_cnt[0])
{
Build(0,1,1,dfs_cnt[0]);
}
if(dfs_cnt[1])
{
Build(1,1,1,dfs_cnt[1]);
}
int opt,u,add_val;
REP(i,1,M)
{
scanf("%d",&opt);
if(opt==1)
{
scanf("%d%d",&u,&add_val);
Updata(TEAM,place[u],place[u]+size[TEAM][u]-1,add_val,1,1,dfs_cnt[TEAM]);//修改奇偶性和这个点一样的部分
if(first_son[u])
{
Updata(TEAM^1,place[first_son[u]/*记录第一个子节点的左右*/],place[first_son[u]]+size[TEAM^1][u]-1,-add_val,1,1,dfs_cnt[TEAM^1]);//奇偶性不同的部分
}
}
if(opt==2)//查询和普通线段树差不多
{
scanf("%d",&u);
printf("%d\n",Query(TEAM,place[u],1,1,dfs_cnt[TEAM]));
}
}
return 0;
}
#undef TEAM

「CF383C Propagating tree」的更多相关文章

  1. Note -「Dsu On Tree」学习笔记

    前置芝士 树连剖分及其思想,以及优化时间复杂度的原理. 讲个笑话这个东西其实和 Dsu(并查集)没什么关系. 算法本身 Dsu On Tree,一下简称 DOT,常用于解决子树间的信息合并问题. 其实 ...

  2. CF383C Propagating tree (线段树,欧拉序)

    \(tag\)没开够\(WA\)了一发... 求出\(dfs\)序,然后按深度分类更新与查询. #include <iostream> #include <cstdio> #i ...

  3. [译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并修改其中的Value 学习笔记——异步 程序员常说的「哈希表」是个什么鬼?

    [译]聊聊C#中的泛型的使用(新手勿入)   写在前面 今天忙里偷闲在浏览外文的时候看到一篇讲C#中泛型的使用的文章,因此加上本人的理解以及四级没过的英语水平斗胆给大伙进行了翻译,当然在翻译的过程中发 ...

  4. loj #535. 「LibreOJ Round #6」花火 树状数组求逆序对+主席树二维数点+整体二分

    $ \color{#0066ff}{ 题目描述 }$ 「Hanabi, hanabi--」 一听说祭典上没有烟火,Karen 一脸沮丧. 「有的哦-- 虽然比不上大型烟花就是了.」 还好 Shinob ...

  5. SpringBoot图文教程17—上手就会 RestTemplate 使用指南「Get Post」「设置请求头」

    有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文教程系列文章目录 SpringBoot图文教程1-Spr ...

  6. 「Netty实战 02」手把手教你实现自己的第一个 Netty 应用!新手也能搞懂!

    大家好,我是 「后端技术进阶」 作者,一个热爱技术的少年. 很多小伙伴搞不清楚为啥要学习 Netty ,今天这篇文章开始之前,简单说一下自己的看法: @ 目录 服务端 创建服务端 自定义服务端 Cha ...

  7. 对于前端,「微信小程序」其实不美好

    微信小程序开放公测了,9月底我曾经写过一篇 「微信小程序」来了,其中最后一句:"谢天谢地,我居然还是个前端". 这种火爆的新事物总是令人激动,感谢这个时代. 但是,当我真作为开发者 ...

  8. macOS安装「oh my zsh」

    目前常用的 Linux 系统和 OS X 系统的默认 Shell 都是 bash,但是真正强大的 Shell 是深藏不露的 zsh, 这货绝对是马车中的跑车,跑车中的飞行车,史称『终极 Shell』, ...

  9. 报名|「OneAPM x DaoCloud」技术公开课:Docker性能监控!

    如今,越来越多的公司开始 Docker 了,「三分之二的公司在尝试了 Docker 后最终使用了它」,也就是说 Docker 的转化率达到了 67%,同时转化时长也控制在 60 天内. 既然 Dock ...

随机推荐

  1. python中GraphViz's executables not found的解决方法以及决策树可视化

    出现GraphViz's executables not found报错很有可能是环境变量没添加上或添加错地方. 安装pydotplus.graphviz库后,开始用pydotplus.graph_f ...

  2. 微信小程序--缓存,支持过期时间的二次开发封装

    简介 微信小程序提供了缓存的api,包括同步和异步两种,具体api不多说明,可自行查看官方文档 现在微信小程序缓存api存在一个问题就是没有设定过期时间,下面给大家介绍一下对小程序缓存的二次封装,使其 ...

  3. nginx autoindex 配置目录浏览功能

    Nginx打开目录浏览功能 yum install httpd-tools -y cd /usr/local/openrestry/nginx/conf/ htpasswd -c passwd adm ...

  4. 创业学习---《如何展开竞争情报调研》--D-1.调研模块---HHR计划---以太一堂

    第一:<开始学习> 1,投资人看人标准:人品好:创业热情:学习能力. 2,思考题:请你预判一个最靠谱的方向来创业,你会怎么调研呢? 3,预热思考题: (1)在这个赛道,究竟有哪些重要竞争对 ...

  5. Ant安装及环境配置

    首先说一下啥事ant 所以装ant的前提是得装java 点击查看怎么安装JDK 然后安装ant,其实挺简单的 官网下载http://ant.apache.org 所有版本https://www.apa ...

  6. SPOJ Distinct Substrings

    给定一个字符串,求不相同子串个数.每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同子串个数.总数为n*(n-1)/2,再减掉height[i]的和就是答案 #include< ...

  7. 微信小程序解析HTML标签带有<p>

    小程序中默认是不支持html格式没有<p>标签,但是有些接口需要返回带有标签的,例如 : 隐私协议: 解决方法: 小程序中有一个富文本标签组件,前端可以解析H5标签就是 rich-text ...

  8. SpringCloud全家桶学习之Feign负载均衡----Feign(四)

    一.Feign概述 (1)Feign是什么? 官网地址:https://projects.spring.io/spring-cloud/spring-cloud.html#spring-cloud-f ...

  9. python匿名函数与三元运算

      匿名函数 匿名函数就是不需要显示式的指定函数名 首先看一行代码: def calc(x,y): return x*y print(calc(2,3)) # 换成匿名函数 calc = lambda ...

  10. python中for循环中的循环变量

    废话不多说,代码伺候: for i in range(3): print("hello") print(i) 运行结果如下: 从上面的例子可以看出,for循环里面的循环变量i作用域 ...