线段树
如果区间信息、标记能够快速合并,可以用线段树
基本储存及实现
储存
封装信息,用info记录存储的信息
后续给val赋值时可使用seg[i].val = {值1,值2…}的形式
typedef long long ll;
const int N = 201000;
struct info {
int minv;
}
struct node {
info val;
} seg[N * 4];
重载加号,确定信息修改规则
info operator + (const info &l, const info &r) {
info a;
a.minv = min(l. minv, r.minv);
return a;
}
这样修改的时候只需要在info里增加变量,在重载里增加信息修改规则就可以了
操作
build() ---- 递归建树
//结点是id,区间是[l, r]
void build(int id, int l, int r) {
if (l == r) {
seg[id].val = {a[l]};
} else {
int mid = (l + r) / 2;
build(id * 2, l , mid);
build(id * 2 + 1, mid + 1, r);
update(id);
}
}
update() ---- 根据左右儿子更新结点的信息
此处的 + 实现了修改信息的功能
void update(int id) {
seg[id].minv = seg[id * 2] + seg[id * 2 + 1];
}
写成函数是为了代码更好的扩展
change() ---- 区间修改
//结点是id,区间是[l, r],a[pos]改为val
void change(int id, int l, int r, int pos, int val) {
if (l == r) {
seg[id].val = {val};
} else {
int mid = (l + r) / 2;
if (pos <= mid) change(id * 2, l, mid, pos, val);
else change(id * 2 + 1, mid + 1, r, pos, val);
update(id);
}
}
query() ---- 查询
//结点是id,区间是[l, r],查询的区间是[ql, qr]
info query(int id, int l, int r, int ql, int qr) {
if (l == ql && r == qr) return seg[id].val;
int mid = (l + r) / 2;
if (qr <= mid) return query(id * 2, l, mid, ql, qr);
else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql, qr);
else {
return query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr);
}
}
例1:线段树1
只需要修改info:
struct info {
int minv, mincunt;
}
重载:
info operator + (const info &l, const info &r) {
info a;
a.minv = min(l. minv, r.minv);
//加入修改mincunt的规则
if (l.minv < r.minv) a.mincunt = l.mincunt;
else if (l.minv > r.minv) a.mincunt = r.mincunt;
else a.mincunt = l.mincunt + r.mincunt;
return a;
}
build和change把递归出口处的赋值改为
seg[id].val = {val, 1};
即可
线段树打标记
存储结构
封装信息,用tag记录标记
手打的话可以不封装info
typedef long long ll;
const int N = 201000;
const ll mod = 1000000007;
struct tag {
ll mul, add;
}
struct info {
ll maxv;
}
struct node {
info val;
tag t;
int sz;
}seg[N * 4];
重载:
-
信息之间的运算
info operator + (const info &l, const info &r) { return {max(l.maxv, r.maxv)}; }
-
信息+标记
info operator + (const info &v, const tag &t) { return {v.maxv * t.mul + t.add}; }
-
标记+标记
注意:t1在t2之前先标记
tag operator + (const tag &t1, const tag &t2) { return {t1.mul * t2.mul % mod, (t1.add * t2.mul + t2.add) % mod}; }
操作
给结点加t标记
void settag(int id, tag t) {
seg[id].val = (seg[id].val * t.mul + seg[id].sz * t.add) %mod;
seg[id].t = seg[id].t + t;
]
标记下传
void pushdown(int id) {
if(seg[id].t.add != 0 || seg[id].t.mul != 1) {
settag(id * 2, seg[id].t);
settag(id * 2 + 1, seg[id].t);
seg[id].t.add = 0;
sge[id].t.mul = 1;
}
}
query需要在递归之前将标记下传
//结点是id,区间是[l, r],查询的区间是[ql, qr]
info query(int id, int l, int r, int ql, int qr) {
if (l == ql && r == qr) return seg[id].val;
int mid = (l + r) / 2;
//加一个标记下传
pushdown(id);
if (qr <= mid) return query(id * 2, l, mid, ql, qr);
else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql, qr);
else {
return (query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr)) % mod;
}
}
区间修改
//结点是id,区间是[l, r],查询的是[ql, qr],标记是t
info modify(int id, int l, int r, int ql, int qr, tag t) {
if (l == ql && r == qr) {
settag(id, t);
return;
}
int mid = (l + r) / 2;
//标记下传
pushdown(id);
if (qr <= mid) modify(id * 2, l, mid, ql, qr);
else if (ql > mid) modify(id * 2 + 1, mid + 1, r, ql, qr);
else {
modify(id * 2, l, mid, ql, mid);
modify(id * 2 + 1, mid + 1, r, mid + 1, qr);
}
update(id);
}
注意所有的查询不需要update,单点修改和区间修改都需要update
build
//加上
seg[id].t = {1, 0};
seg[id].sz = r - l + 1;
线段树上二分
例:找出[l, r]中第一个大于d的数字
int serch(int id, int l, int r, int ql, int qr, int d) {
if(l == ql && r == qr) {
if(seg[id].val < d) return -1;
else {
if(l == r) return l;
int mid = (l + r) / 2;
if(seg[id * 2].val >= d) return search(id * 2, l, mid, ql, mid, d);
else return search(id * 2 + 1, mid + 1, r, mid + 1, qr, d);
}
}
int mid = (l + r) / 2;
if(qr <= mid) return search(id * 2, l, mid, ql, qr, d);
else if(ql > mid) return search(id * 2 + 1, mid + 1, r, ql, qr, d);
else {
int pos = search(id * 2, l, mid, ql, mid);
if(pos == -1) return search(id * 2 + 1, mid + 1, r, mid + 1, qr);
else return pos;
}
}