以一个题来开场吧——扩展二叉树
(其实什么用也没有)
描述
由于先序、中序和后序序列中的任一个都不能唯一确定一棵二叉树,所以对二叉树做如下处理,将二叉树的空结点用·补齐,如图所示。我们把这样处理后的二叉树称为原二叉树的扩展二叉树,扩展二叉树的先序和后序序列能唯一确定其二叉树。
现给出扩展二叉树的先序序列,要求输出其中序和后序序列。
扩展二叉树的先序序列。
输出其中序和后序序列。
ABD..EF..G..C..
DBFEGAC
DFGEBCA
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdlib>
#include<map>
using namespace std;
typedef struct node;
typedef node *tree;
struct node
{
char data;
tree lchild,rchild;
};
tree bt;
int i;
string s;
void build(tree &bt)
{
if(s[++i]!='.')
{
bt =new node;
bt->data=s[i];
build(bt->lchild);
build(bt->rchild);
}
else bt=NULL;
}
void printzx(tree bt)
{
if(bt)
{
printzx(bt->lchild);
cout<<bt->data;
printzx(bt->rchild);
}
}
void printhx(tree bt)
{
if(bt)
{
printhx(bt->lchild);
printhx(bt->rchild);
cout<<bt->data;
}
}
int main()
{
cin>>s;
i=-1;
build(bt);
printzx(bt);
cout<<endl;
printhx(bt);
cout<<endl;
return 0;
}
听了扶苏老爷今天讲的线段树(扶苏课堂
有感而发准备总结一波
(顺带谈谈BFO感想)
线段树作为一种特殊的二叉树
运用了二分的整体思想
将一个序列对半分
再对半分一直到每个小序列是[l,l]也就是只包含它一个数为止
这些数就是这颗线段树的叶节点
不难明白一颗由长度为n的序列建出的线段树的叶节点有n个
则全部的节点数为2n-1
比如序列1,2,3
[1,3]->[1,2],[2,3]->[1,1][2,2][3,3]
那我们看完了基本定义
依靠一个例题来看看线段树的基本操作
洛谷 P3372 【模板】线段树 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
- 将某区间每一个数加上 kk。
- 求出某区间每一个数的和。
输入格式
第一行包含两个整数 n, mn,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 nn 个用空格分隔的整数,其中第 ii 个数字表示数列第 ii 项的初始值。
接下来 mm 行每行包含 33 或 44 个整数,表示一个操作,具体如下:
1 x y k
:将区间 [x, y][x,y] 内每个数加上 kk。2 x y
:输出区间 [x, y][x,y] 内每个数的和。
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
输入输出样例
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
11
8
20
说明/提示
对于 30\%30% 的数据:n le 8n≤8,m le 10m≤10。
对于 70\%70% 的数据:n le {10}^3n≤103,m le {10}^4m≤104。
对于 100\%100% 的数据:1 le n, m le {10}^51≤n,m≤105。
保证任意时刻数列中任意元素的和在 [-2^{63}, 2^{63})[−263,263) 内。
【样例解释】
我们考虑使用线段树来实现题目要求(废话这题就是线段树板子)
那我们先提一嘴线段树的基本操作——区间修改和查询区间和
(单点修改和单点查询其实就是区间内只有一个数的情况)
如何实现需要降到讲到Lazytap(懒标记)以及父子节点信息的传递
我们对着代码讲
//先怼一波扶苏老爷的毒瘤码风 #include <cstdio> const int maxn = 100005; typedef long long int ll; int n, q; ll a[maxn];//原数组 struct Node { ll tag, v;//延迟标记,子节点值 int l, r;//左端点,右端点 Node *ls, *rs;//左孩子、右孩子 inline void maketag(const ll w) {//给一个结点打延迟标记 v += (r - l + 1) * w;//更新当前结点值 tag += w;//延迟标记 } inline void pushup() {//上传信息 v = ls->v + rs->v;//当前结点值=左结点值+右结点值 } inline void pushdown() {//下传延迟标记 if (tag == 0) return;//当前结点没有延迟标记,直接返回 ls->maketag(tag);//向左右孩子打标记 rs->maketag(tag); tag = 0;//清空当前结点延迟标记 } Node(const int L, const int R) {//构造函数 l = L; r = R; //当前结点左右端点 if (l == r) {//叶子结点 tag = 0;//清空延迟标记 v = a[l];//其值等于这个结点对应的值本身 ls = rs = NULL;//没有左右孩子结点 } else { tag = 0; int M = (L + R) >> 1;//取中点 ls = new Node(L, M);//递归向下一层进行构造 rs = new Node(M + 1, R); pushup();//值等于左右孩子结点值的和 } } // this l, r inline bool InRange(const int L, const int R) { return (L <= l) && (r <= R); } //一个结点被完全包含 inline bool OutofRange(const int L, const int R) { return (l > R) || (r < L); } //一个结点完全没有重叠(毫不相关) void upd(const int L, const int R, const ll w) {//赋值操作 if (InRange(L, R)) {//完全包含 maketag(w);//先打上延迟标记 } else if (!OutofRange(L, R)) {//有重叠部分但没有完全包含 pushdown();//下传延迟标记到子节点 ls->upd(L, R, w);//递归更改信息 rs->upd(L, R, w); pushup();//向上传递信息 } } ll qry(const int L, const int R) {//询问一段区间的和 if (InRange(L, R)) return v;//完全包含直接返回这个结点的值 if (OutofRange(L, R)) return 0;//毫不相关,返回0 pushdown();//下传 return ls->qry(L, R) + rs->qry(L, R);//递归询问 } }; int main() { scanf("%d%d", &n, &q); for (int i = 1; i <= n; ++i) scanf("%lld", a + i); Node *rot = new Node(1, n); for (ll o, x, y, z; q; --q) { scanf("%lld%lld%lld", &o, &x, &y); if (o == 1) { scanf("%lld", &z); rot->upd(x, y, z); } else { printf("%lld ", rot->qry(x, y)); } } return 0; }
其中最需要说明的应该是lazytap的使用
他的大体意思是说
如果我们要对一个区间都加上一个数
那么我们在这个区间所对应的父节点上打上一个懒标记
同时修改这个区间的区间和+=区间长度*这个数
但同时并不改变这段区间内的任何子节点
查询区间和的时候会有两种情况
这段被标记的区间恰好在需要查询的区间中
那我们直接访问它的区间和就好了
如果没在(全没在或者有一部分在)呢?
很显然
全没在的话根本不需要调用这一块被标记的区间
如果是有一部分被包含在查询区间内
那我们把这个懒标记传递给它的左右两个子节点
想一想这样是没有问题的对吧
一个区间对半分成两半
每一半里的每一个数还是加上了x
所以两者是等价的
举个例子区间[1,2]被我们打上了一个+1的lazytap
我们需要调用[1,1]这个区间
那我们把[1,2]拆成[1,1]和[1,2]
每个数都加上1
就可以得到带着懒标记的[1,1]
就这样一直递归到需要访问的区间完全在查询区间内即可提出区间和
最重要的是千万别忘了在把懒标记传递给左右两个子节点的时候
一定一定要擦去父节点上的懒标记
否则就会出现反复递归的诡异局面
最后合并区间的时候把两个区间逐步和起来就可以查询到大区间的区间和了
怎么样是不是很简单
其实线段树算是比较复杂(对新手)的数据结构了
所以一时不理解也没有关系
可以画画图或手动copy几遍线段树板子的代码
这样可以帮助你很快滴理解线段树和他的(基本)操作
~End~