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

题目大意

给出一颗根节点编号为 \(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. 【js】子菜单吸顶(滚动到一定距离 容器置顶)附 无间断置顶 避免闪动

    思考逻辑:当向上滚动的高度>= 观察容器距离顶部高度 即可触发吸顶 以下代码在vue工程,作参考 handleScroll () { const scrollTop = window.pageY ...

  2. Scrapy爬取伯乐在线的所有文章

    本篇文章将从搭建虚拟环境开始,爬取伯乐在线上的所有文章的数据. 搭建虚拟环境之前需要配置环境变量,该环境变量的变量值为虚拟环境的存放目录 1. 配置环境变量 2.创建虚拟环境 用mkvirtualen ...

  3. 395. 至少有K个重复字符的最长子串

    Q: A: 分治,对于字符串s的任何一个字符,如果它的频数(在s中出现的次数)小于k,则它一定不会出现在最后的结果里,也就是从它的位置一劈两半,考察左右.对于当前字符串s,我们先建立字典统计其中每种字 ...

  4. Js将字符串转换成对象或数组en

    举个例子 var test='{ colkey: "col", colsinfo: "NameList" }' a.将文本转换成对象 var test='{ c ...

  5. 【网易官方】极客战记(codecombat)攻略-地牢-恐惧之门

    关卡连接: https://codecombat.163.com/play/level/dread-door 恐惧之门后藏满宝藏 简介: while-true 循环可以使用任何方法,如: while ...

  6. jmeter的使用---录制脚本

    1.设置fidder 2.在fidder中导出请求,选择jmx格式

  7. jquery动画系统

    1.隐藏显示的方法: $(selector).show(speed,callback); $(selector).hide(1000); $(selector).toggle("slow&q ...

  8. Codeforces Round #598 (Div. 3) F. Equalizing Two Strings

    You are given two strings ss and tt both of length nn and both consisting of lowercase Latin letters ...

  9. IDEA 自定义注释模板 支持设置多个param参数

    在使用IDEA过程中,很多人可能感觉自带注释太简约了,想增加一些属性,比如作者.创建时间.版本号等等,这个时候我们可以使用自定义的注释模板来实现我们需求,话不多说直接进入如何自定义模板设置: 打开设置 ...

  10. CentOS7重启和关机

    重启命令: 1.reboot 2.shutdown -r now 立刻重启(root用户使用) 3.shutdown -r 10 过10分钟自动重启(root用户使用) 4.shutdown -r 2 ...