寒讯内容有点过多(其实是我太菜了)
水一波怕忘了(人老了)
**什么是线段树**
线段树是本蒟蒻感觉用处特别大的算法
那么线段树上面的节点表示什么意思呢?
线段树,上面的节点表示一个区间,父亲节点表示的区间是左右儿子相加.
同一层的节点所代表的区间,相互不会重叠.一层节点所代表的区间,加起来是个连续的区间
下面来张图看看线段树的建树过程
该图片来自秦淮案大佬的博客
那么这么牛叉的数据结构有什么用处呢?
通过观察可以发现线段树将一个连续的区间切割成了一个个子区间那么它的作用当然是和区间的操作有过:比如说区间的四则运算,区间值数值查询…(我目前就会这么多。。)
下面来贴代码啦(。——。)
千里之行始于建树
/先来一波准备操作/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define root rt;//宏定义左右儿子
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define m(a,b) memset((a),(b),sizeof(a))
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int M = 5e4 + 10;
int temp;
int ps[M << 2];//底层子节点
ll sum[M << 2];//四倍空间
这里要提一下就是线段树的空间问题
通过观察可知加入线段树有n层
那么边数就是2^0 + 2 ^ 1 + 2 ^ 2 + …2 ^ n - 1 = 2 ^ n - 1;(等差数列求和)
那么底层的子节点有 2 ^ n - 1;
按理说 空间 = 底层节点 * 2(????)
不幸的是本蒟蒻第一次开就RE了(自闭 )
下面是某位大佬的证明
假设我们N个连续的节点需要建立线段树,如果最下层有节点,那么最左边一定不是空的。而最左边节点也就是左儿子是:[l,(l+r)>>1][l,(l+r)>>1]。因此就是(1+N)(1+N)不停/2/2。那么线段树的深度为log21+Nlog21+N
假如是满二叉树,那么节点数为
2^(log21+N)
好吧我也看不懂反正开四倍就对了(滑稽.jpg)
这是建树阶段咯
建树口诀 ->build(左右儿子) ->push_up(爸爸);
inline void Push_up(int rt)//有点像分治法
{
sum[rt] = sum[rt << 1] + sum[rt << 1|1];//这里是位运算
//左移相当*2 与 相当+1
}
inline void build(int rt, int l, int r)
{
if(l == r)
{
sum[rt] = ps[l];
return ;
}
build(Lson);//先更新左右儿子;
build(Rson);
Push_up(rt);//更新后别忘了pust_up更新
}
下面先来一波常规操作
单点修改gen
单点修改口诀 -> 等改回 -> 二分查-> 更父值//最后一步个人认为比较容易忘
inline void Change(int rt, int l, int r, int x, int v)
//就是将 x 点 的权值修改为 v;
{
if(l == r)//树的层次遍历
{//早到这个点就改使劲改 改完就跑
sum[rt] += v;
return ;
}
if(x <= mid) Change(Lson,x,v);//通过区间的找爸爸的儿子在哪 类比二三分理解
else Change(Rson,x,v);
Push_up(rt);
}
如果我们要查询呢?
因为父节点就存储了子节点的查询性质那么我们只要将查询的区间分割
再分别递归查询
inline ll Query_sum(int rt, int l, int r, int posl, int posr)
// 后面两个是当前的位置
{
int ans = 0;
if(posl <= l && r <= posr) //目标区间包含了当前区间
return sum[rt];
if(posl <= mid)// 目标区间有一部分在右边
ans += Query_sum(Lson,posl,posr);
if(posr > mid)//目标区间有一部分在右边
ans += Query_sum(Rson,posl,posr);
Push_up(rt);
return ans;
}
(好啦今天就这样吧太晚了,明天再更区间修改,用空水一波自己没想出来的题解);
持续更新…
(吐槽一下 csdn的夜间审核功能------比较慢)
线段树的区间修改问题
首先我们引入这样一个问题:我们要对线段树的一段区间的数值进行修改(比如说 + 上delta),如果我们每一个点都去修改线段树的时间复杂度就会退化到O(nlogn)【因为递归(logn)* 修改(n】
比直接建数组修改数组时间O(n)还可怕,这是绝大多数算法题不可以接受的时间复杂度,那么线段树就有一个创新性的用法:(懒惰标记)。
下面来看一张图理解一下
假如我们想将区间[3,7]的值都加上delta,我们可以发现[3,7]可以拆分成[3,4],[5,6],[7,7]而[3,4],[5,6],[7,7]分别对应上图中的5,6,14号节点,因此我们只需要将这几个节点进行修改。懒惰标记用于存储父节点的修改信息, 但暂时不把信息传给子节点,等到需要用到子节点时再把信息传给子节点。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define root rt;
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int M = 1e5 + 10;
ll sum[M << 2];
ll num[M << 2];
ll tag[M << 2];//懒惰标记
inline void push_up(int rt)
{
sum[rt] = sum[rt << 1]+ sum[rt << 1 | 1];
}
inline void Build(int rt,int l, int r)
{
if(l == r)
{
sum[rt] = num[l];
return;
}
Build(Lson);
Build(Rson);
push_up(rt);
}
建树上和单点修改的没有什么区别定义那里多定义了一个tag数组是用来懒惰标记的,精华在下面这一坨里面;
( 顾名思义(就是信息向下传输))
我们看一下操作顺序
1.首先判断一下父节点上是否又标记就是区间修改的信息
2.将父节点的tag值传给子节点~~(注意一下这里是 tag[left] += lazy, 我之前老是打成=号qwq)~~
3.更新一下左右儿子的sum = lazy * (区间长度 || 儿子的个数)
4.记得哦 因为你把父节点的数值传递下去了 所以要把父节点的tag变成0
防止重复计算
inline void push_down(int rt, int l, int r)
{//精髓在与只递归被父节点覆盖的下一层
if(tag[rt])//如果父节点被覆盖
{
ll lazy = tag[rt];
int left = rt << 1, right = rt << 1|1;
//左右孩子加上父节点的tag值
tag[left] += lazy,tag[right] += lazy;
sum[left] += lazy * (mid - l + 1);
sum[right] += lazy * (r - mid);
tag[rt] = 0;//把父节点的值清空 防止 重复加
}
}
// 将区间[start,end]的值都加上delta
// 如果当前区间[l,r]是[start,end]的真子集,那么直接修改当前节点对应的区间不用递归向下修改了
// 记住,我们修改的值包括区间和(tree数组)和延迟标记(tag数组)
// 否则我们需要递归查找当前节点rt的左右孩子,在进行递归查找之前我们需要先将当前节点已经存在的延迟标记传下去(push_down函数)。
// 然后再根据左子树是否有目标区间的元素和右子树是否有目标区间的元素进行递归修改。
// 最后不要忘记更新当前节点的值
inline void updateRange(int rt,int l,int r, int posl,int posr,ll add)
{
// posl 是你要修改的区间长度下标
if(posl <= l && posr >= r)//如果当前的位置被查询的区间赋盖
{
sum[rt] += add * (r - l + 1);//区间长度
tag[rt] += add; //表示下面的点的都要加add
//待会要查询的时候直接 把tag的值传下去
return ;
}
push_down(rt,l,r);
if(posl <= mid) updateRange(Lson,posl,posr,add);
if(posr >= mid + 1) updateRange(Rson,posl,posr,add);
push_up(rt);
}
1.判断一下当前递归的区间是否被要求修改的区间全部包含
if包含了就直接修改该节点,改完就return
else 关键一步就是push_down 就是将父节点的tag传下去告诉儿子爸爸来了
2.再去递归修改
inline ll rangeQuery(int rt,int l,int r, int posl,int posr)
{
if(posl <= l && posr >= r)
return sum[rt];
push_down(rt,l,r);//如果还没有包含
//就要继续向下递归但是向下递归的时候要把父节点的tag值传下去
ll res = 0;
if(posl <= mid)//虽然没有覆盖到 全部但是覆盖了子树
res += rangeQuery(Lson,posl,posr);
if(posr >= mid + 1)
res += rangeQuery(Rson,posl,posr);
return res;
}
1.首先依旧是判断是否包含
如果包含了就直接return sum[rt];
2.如果没有就要继续向下递归但是向下递归的时候要把父节点的tag值传下去
3.定义一个res值去接受子节点的信息 并且存储答案
这里补充一点 所有的函数都有(rt, l, r)除了push_up
szu线段树练习题解个人版
poj2528
Mayor’s Poster
题目大意是说,有一堵长为10000000,现在要在墙上贴(1 ≤ ≤ 10000)张海报 (海报与海报之间会相互覆盖),问贴完所有海报后,能看到的海报有多少张? 保证每张海报的左右端点都不会超出范围[1,17]
解题思路:
首先我们看一下目标区间的长度位1e7 但是 海报只有1e4张 大约位1000倍
而且我们对长度为1e7的区间建立线段树是不实际的,而且题意是说是看到多少张海报(只考虑先后顺序不考虑值的具体数字是多少)那么我们就可以将区间离散化一遍 -> 1e4张海报点点数最多为 2e4次方 (那么我们就可以建立线段树了)
举个例子
[1,100] , [2 , 20] ,[3,100];
1.看一下里面有多少个不同的数字
1,2,3,20,100;(离散化本质是一种特殊的哈希方式)原值
1,2,3,4,5;离散化后的数值
上面的三个区间就变成了
[1,5] 和 [2,4] 和 [3,5];
这样我们就可以发现区间的相对关系是不变的
离散化的话有两种写法
第一种是
//离散化预处理
inline void unique()//去重
{
//排序
sort(a + 1,a + n + 1);
//去重
for(int i = 1;i <= n;++i)
{
if(i == 1 || a[i] != a[i-1])
b[++m] = a[i];
}
}
//二分查找 x映射为那个1~m之间的整数
inline int query(int x)
{
return lower_bound(b + 1,b + m + 1,x) - b;
}
一种是用vector
这是我个人比较喜欢的离散化方式
#include
#include
#include
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N = 3e5 + 10;
int n, m;
int a[N], s[N];
vector<LL> alls;
vector<PII> add, query;
int find(int x) // 查找离散化之后的结果
{
int l = 0, r = alls.size() - 1;
while(l < r)
{
int mid = (l + r) >> 1;
if(alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1;
//因为要用到前缀和所以下标要离散化到1以后的自然数
}
vector<int>::iterator unique(vector<int> & a)//这是模拟手写一下
//c++自带unqiue函数
{
int j = 0;
for(int i = 0; i < a.size(); ++ i)
if(!i || a[i] != a[i - 1])
a[j ++ ] = a[i];
return a.begin() + j;
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; ++ i)//先读入所有的操作
{
int x, c;
cin >> x >> c;
add.push_back({x,c});
alls.push_back(x);//见下标存入vector中准备离散化
}
for(int i = 0; i < m; ++ i)
{
int l, r;
cin >> l >> r;
query.push_back({l,r});
alls.push_back(l);//将询问的区间也加进去离散化
alls.push_back(r);
}
//去重 先sort
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
for(auto it : add)//对离散化后的坐标进行前缀和
{
int x = find(it.first);
a[x] += it.second;
}
//处理前缀和
for(int i = 1; i <= (int)alls.size(); i ++ )
s[i] = s[i - 1] + a[i];
for(auto it : query)
{
int l = find(it.first);
int r = find(it.second);
cout << s[r] - s[l - 1] << endl;
}
return 0;
}
下面还有一个问题就是如何去查询答案???
以什么基准去查询答案???
在正常的贴海报的过程中我们要去计算我们这次贴海报会把多少海报覆盖
这样操作是非常麻烦的
反过来想 我们倒这贴呢?
最后一张海报是一定可以看见的,倒数第二张如果不被倒数第一张完全覆盖
那么它也一定是可以看见的
那么我们就可以用lazy标记从最后一张海报去维护答案
思路有了开始打咯(qwq)
#include
#include
#include
#include
#include
#include
#include
#define INF 0x3f3f3f3f
#define m(a,b) memset((a),(b),sizeof(a))
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
using namespace std;
typedef pair<int,int> PII;
const int N = 1e5 + 10;
PII pos[N];
int sum[N << 2], n;
vector<int> ls;
int ans;
int find(int x)
{
int l = 0, r = ls.size() - 1;
while(l < r)
{
if(ls[mid] >= x) r = mid;
else l = mid + 1;
}
return l + 1;
}
inline int undateRange(int rt,int l, int r, int posl, int posr)
{
if(sum[rt]) return 0;//如果这块已经后面的贴了就不用递归
if(posl <= l && posr >= r)
{
if(sum[rt]) return 0;
else
{
sum[rt] = 1;
return 1;
}
}
int res1 = 0, res2 = 0;//记得初始化再打一遍的时候没有初始化wa了一发
因为下面的递归不一定执行那么res1 和 res2 是没有数值的
if(posl <= mid)
res1 = undateRange(Lson,posl,posr);
if(posr >= mid + 1)
res2 = undateRange(Rson,posl,posr);
if(sum[rt << 1] && sum[rt << 1|1]) sum[rt] = 1;//更新操作
return (res1 || res2);//如果有一块露出来就是1
}
int main()
{
int T;
scanf("%d",&T);
while(T --)
{
ls.clear(); m(sum,0);
ans = 0;
scanf("%d",&n);
for(int i = 1; i <= n; ++ i)
{
int l, r;
scanf("%d%d",&l,&r);
pos[i] = {make_pair(l,r)};
ls.push_back(l); ls.push_back(r);
}
sort(ls.begin(),ls.end());
ls.erase(unique(ls.begin(),ls.end()),ls.end());
for(int i = n; i >= 1; -- i)
{
int L = find(pos[i].first);
int R = find(pos[i].second);
ans += undateRange(1,1,ls.size(),L,R);
}
printf("%d\n",ans);
}
return 0;
}
这是集训里面pdf上面的聚聚的代码
他是正着做的好吧不太明白
线段树就更这么哆啦;区间扫描线等学完计算几何再回来更了
原题链接
线段树
区间最大连续字段和是一道非常经典的问题,这道题目中我们唯一与线段树模板不同的地方,也就是这里.
最大连续子段和,根据区间可见性,我们知道这里面必然会增加两个变量lmax和rmax分别管理前缀最大子段和和后缀最大子段和.然后根据区间可见性,显然[l,r]区间的最大子段和就是1.左区间的最大子段和,3.右区间最大子段和,以及2.左右两区间结合在一起中间的最大子段和.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define sfx(x) scanf("%lf",&x)
#define sfxy(x,y) scanf("%lf%lf",&x,&y)
#define sdx(x) scanf("%d",&x)
#define sdxy(x,y) scanf("%d%d",&x,&y)
#define pfx(x) printf("%.0f\n",x)
#define pfxy(x,y) printf("%.6f %.6f\n",x,y)
#define pdx(x) printf("%d\n",x)
#define pdxy(x,y) printf("%d %d\n",x,y)
#define _for(i,a,b) for( int i = (a); i < (b); ++i)
#define _rep(i,a,b) for( int i = (a); i <= (b); ++i)
#define for_(i,a,b) for( int i = (a); i >= (b); -- i)
#define rep_(i,a,b) for( int i = (a); i > (b); -- i)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define hash Hash
#define next Next
#define f first
#define s second
using namespace std;
const int N = 5e5 + 10, eps = 1e-10;
typedef long long LL;
typedef unsigned long long ULL;
int n, m, a ,len;
struct Node {
int l, r;//区间左右儿子
int sum, lmax, rmax, tmax;//该区间的区间和,该区间最大前缀和以及该区间最大后缀和
}tr[N << 2];//以及该区间内最大连续子段和tmax
int w[N];
//分治法求最大连续子段和
void pushup(Node &u, Node &l, Node & r)
{
u.sum = l.sum + r.sum;
u.lmax = max(l.lmax,l.sum + r.lmax);
u.rmax = max(r.rmax, r.sum + l.rmax);
//一个数组的最大前缀和,可能没过左儿子的长度也可能过了左儿子延申到右儿子前缀
u.tmax = max(max(l.tmax,r.tmax),l.rmax + r.lmax);
}
void pushup(int u)
{
pushup(tr[u],tr[u << 1],tr[u << 1|1]);
}
void build(int rt,int l,int r)
{
if(l == r)
{
tr[rt] = {l,r,w[r],w[r],w[r],w[r]};
return;
}
tr[rt] = {l,r};
build(Lson), build(Rson);
pushup(rt);
}
void modify(int u, int pos, int val)
{
if(tr[u].l == pos && tr[u].r == pos)
{
tr[u] = {pos,pos,val,val,val,val};
return;
}
int m = tr[u].l + tr[u].r >> 1;
if(pos <= m)
modify(u << 1, pos, val);
if(pos > m)
modify(u << 1|1, pos, val);
pushup(u);
}
Node query(int u, int l, int r)
{
if(tr[u].l >= l && tr[u].r <= r) return tr[u];
else
{
int m = tr[u].l + tr[u].r >> 1;
if(r <= m) return query(u << 1, l, r);
else if(l > m) return query(u << 1|1,l,r);
else
{
auto left = query(u << 1, l, r);
auto right = query(u << 1|1, l, r);
Node res;
pushup(res, left, right);
return res;
}
}
}
int main()
{
IOS;
cin >> n >> m;
_for(i,1,n+1) cin >> w[i];
build(1,1,n);
int k, x ,y;
while(m --)
{
cin >> k >> x >> y;
if(k == 1)
{
if(x > y) swap(x,y);
cout << query(1, x, y).tmax << endl;
}
else modify(1,x,y);
}
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define sfx(x) scanf("%lf",&x)
#define sfxy(x,y) scanf("%lf%lf",&x,&y)
#define sdx(x) scanf("%d",&x)
#define sdxy(x,y) scanf("%d%d",&x,&y)
#define pfx(x) printf("%.0f\n",x)
#define pfxy(x,y) printf("%.6f %.6f\n",x,y)
#define pdx(x) printf("%d\n",x)
#define pdxy(x,y) printf("%d %d\n",x,y)
#define _for(i,a,b) for( int i = (a); i < (b); ++i)
#define _rep(i,a,b) for( int i = (a); i <= (b); ++i)
#define for_(i,a,b) for( int i = (a); i >= (b); -- i)
#define rep_(i,a,b) for( int i = (a); i > (b); -- i)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define hash Hash
#define next Next
#define f first
#define s second
using namespace std;
const int N = 5e5 + 10, eps = 1e-10;
typedef long long LL;
typedef unsigned long long ULL;
int n, m, a ,len;
//gcd(a1,a2,a3,a4...an) = gcd(a1,a2 - a1, a2 - a3, a4 - a3)
//假设d为a1 到 an的约数,那么(a2 - a1) / d也是整数
//区间修改就变成了单点修改
LL w[N << 2];
struct Node {
int l, r;
LL sum, d;
}tr[N << 2];
void pushup(Node & u, Node & l, Node & r)
{
u.sum = l.sum + r.sum;
u.d = __gcd(l.d,r.d);
}
void pushup(int rt)
{
pushup(tr[rt],tr[rt << 1],tr[rt << 1|1]);
}
void build(int rt, int l, int r)
{
if(l == r)
{
LL b = w[r] - w[r - 1];
tr[rt] = {l, r, b, b};
return;
}
tr[rt] = {l,r};
build(Rson), build(Lson);
pushup(rt);
}
Node query(int rt, int l, int r)
{
if(l > r) return {0,0,0,0};
if(tr[rt].l >= l && tr[rt].r <= r) return tr[rt];
else
{
int m = tr[rt].l + tr[rt].r >> 1;
if(r <= m) return query(rt << 1, l, r);
else if (l > m) return query(rt << 1|1 , l, r);
else
{
auto left = query(rt << 1, l, r);
auto right = query(rt << 1|1 , l, r);
Node res;
pushup(res,left,right);
return res;
}
}
}
void add(int rt, int pos, LL val)
{
if(pos > n) return;
if(tr[rt].l == pos && tr[rt].r == pos)
{
LL b = tr[rt].sum + val;
tr[rt] = {pos,pos,b,b};
return;
}
int m = tr[rt].l + tr[rt].r >> 1;
if(pos <= m)
add(rt << 1, pos, val);
if(pos > m)
add(rt << 1|1, pos, val);
pushup(rt);
}
int main()
{
IOS;
cin >> n >> m;
_for(i,1,n+1) cin >> w[i];
build(1,1,n);
while(m --)
{
char op;
LL l, r, d;
cin >> op;
if(op == 'Q')
{
cin >> l >> r;
auto left = query(1,1,l);
auto right = query(1,l+1,r);
cout << abs(__gcd(left.sum,right.d)) << endl;
}
else
{
cin >> l >> r >> d;
add(1,l,d);
add(1,r+1,-d);
}
}
return 0;
}