标签:line 标记 有序 最优 else seq void 创建 大小
//区间更新求和
inline int ls(int p) { return p << 1; }//左儿子
inline int rs(int p) { return p << 1 | 1; }//右儿子
void push_up(int p) {
t[p] = t[ls(p)] + t[rs(p)];
}// 向上不断维护区间操作
void build(ll p, ll l, ll r) {
if (l == r) { t[p] = a[l]; return; }
ll mid = (l + r) >> 1;
build(ls(p), l, mid);
build(rs(p), mid + 1, r);
push_up(p);
}
inline void f(ll p, ll l, ll r, ll k) {
tag[p] = tag[p] + k;
t[p] = t[p] + k * (r - l + 1);
}
inline void push_down(ll p, ll l, ll r) {
ll mid = (l + r) >> 1;
f(ls(p), l, mid, tag[p]);
f(rs(p), mid + 1, r, tag[p]);
tag[p] = 0;
}
inline void update(ll ul, ll ur, ll l, ll r, ll p, ll k) {
//ul,ur为要修改的区间
//l,r,p为当前节点所存储的区间以及节点的编号
if (ul <= l && r <= ur) {
t[p] += k * (r - l + 1);
tag[p] += k;
return;
}
push_down(p, l, r);
ll mid = (l + r) >> 1;
if (ul <= mid)update(ul, ur, l, mid, ls(p), k);
if (ur>mid) update(ul, ur, mid + 1, r, rs(p), k);
push_up(p);
}
ll query(ll ql, ll qr, ll l, ll r, ll p) {
ll res = 0;
if (ql <= l && r <= qr)return t[p];
ll mid = (l + r) >> 1;
push_down(p, l, r);
if (ql <= mid)res += query(ql, qr, l, mid, ls(p));
if (qr>mid) res += query(ql, qr, mid + 1, r, rs(p));
return res;
}
区间rmq即其他符合结合律的运算,支持区间更新。
【二分】找第k个0:结点维护0的个数cnt[rt],然后在树上队cnt二分。
【二分】找第k大(权值线段树):这个问题可以排序或nth_element(start,start+first,start+last)
.
考虑线段树的做法,我们先将n个数离散化到[1,n]范围.建一个1~n的空树,每个结点维护tot即该区间有几个数。然后将n个数字依次插入:
void insert(int x,int root = 1)
{
if(seg[root].l == seg[root].r)
{
set[root].tot++;
return ;
}
int mid = seg[root].l + seg[root].r >> 1;
if(x <= mid)//在左儿子当中
insert(k,lson);
if(x > mid)//在右儿子当中
insert(k,rson);
//Push up the information
}
查找:
int query(int k,int root = 1)
{
if(seg[root].l == seg[root].r)
return seg[root].l;//找到了
int mid = seg[lson].tot;//左儿子区间的数字个数
if(k <= mid)
return query(k,lson);//左儿子里面找
else if(k > mid)
return query(k - mid,rson);//右儿子里面找
}
这就是权值线段树,所有叶子节点相当于一个统计每个数出现次数的cnt数组,然后再把这些信息求和地push_up
【二分】给定x,找最小的 i 使得\(prefixSum[i]>x\):树上二分即可。
【dp】找最大子段和。你可以dp O(n)做,也可以线段树维护四个值:区间和,区间最大子段和,最大前缀,最大后缀。
push_up 这么写(dp的感觉)。
struct data {
int sum, pref, suff, ans;
};
data combine(data l, data r) {
data res;
res.sum = l.sum + r.sum;
res.pref = max(l.pref, l.sum + r.pref);
res.suff = max(r.suff, r.sum + l.suff);
res.ans = max(max(l.ans, r.ans), l.suff + r.pref);
return res;
}
【树套数据结构】无序区间lower_bound():每个结点存该区间的有序序列。\(O(n \log n)\)空间
vector<int> t[4*MAXN];
//O(n log n)建树
void build(int a[], int v, int tl, int tr) {
if (tl == tr) {
t[v] = vector<int>(1, a[tl]);
} else {
int tm = (tl + tr) / 2;
build(a, v*2, tl, tm);
build(a, v*2+1, tm+1, tr);
merge(t[v*2].begin(), t[v*2].end(), t[v*2+1].begin(), t[v*2+1].end(),
back_inserter(t[v]));
}
}
//O(log^2n)询问
int query(int v, int tl, int tr, int l, int r, int x) {
if (l > r)
return INF;
if (l == tl && r == tr) {
vector<int>::iterator pos = lower_bound(t[v].begin(), t[v].end(), x);
if (pos != t[v].end())
return *pos;
return INF;
}
int tm = (tl + tr) / 2;
return min(query(v*2, tl, tm, l, min(r, tm), x),
query(v*2+1, tm+1, tr, max(l, tm+1), r, x));
}
//若想单点更新,每个结点存multiset 建树时空花费为O(nlog^2n)
void update(int v, int tl, int tr, int pos, int new_val) {
t[v].erase(t[v].find(a[pos]));
t[v].insert(new_val);
if (tl != tr) {
int tm = (tl + tr) / 2;
if (pos <= tm)
update(v*2, tl, tm, pos, new_val);
else
update(v*2+1, tm+1, tr, pos, new_val);
} else {
a[pos] = new_val;
}
}
【fractional cascading】 把上一个问题优化为\(O(\log n)\)。
考虑子问题:有n个数,被分成k个有序序列,分别对每个序列询问lower_bound,如何将\(O(klog\frac{n}{k})\) 优化为\(O(logn)\)?
一个想法是对每个数预处理它在其它k-1个序列中lower_bound的结果(假装没有时间花费吧,暂时想不出有什么好办法。)。这样直接对合并的序列直接二分,然后查预处理的表即可。不过空间花费为O(kn)
如何优化为O(n)??? 看不懂。。。
【二维】单点修改,询问某个子矩阵最大值。先对x坐标建树tree_x,对它的每个结点都建一个线段树tree_y
//空间O(16nm)
void build_y(int vx, int lx, int rx, int vy, int ly, int ry) {
if (ly == ry) {
if (lx == rx)
t[vx][vy] = a[lx][ly];
else
t[vx][vy] = t[vx*2][vy] + t[vx*2+1][vy];
} else {
int my = (ly + ry) / 2;
build_y(vx, lx, rx, vy*2, ly, my);
build_y(vx, lx, rx, vy*2+1, my+1, ry);
t[vx][vy] = t[vx][vy*2] + t[vx][vy*2+1];
}
}
void build_x(int vx, int lx, int rx) {
if (lx != rx) {
int mx = (lx + rx) / 2;
build_x(vx*2, lx, mx);
build_x(vx*2+1, mx+1, rx);
}
build_y(vx, lx, rx, 1, 0, m-1);
}
int sum_y(int vx, int vy, int tly, int try_, int ly, int ry) {
if (ly > ry)
return 0;
if (ly == tly && try_ == ry)
return t[vx][vy];
int tmy = (tly + try_) / 2;
return sum_y(vx, vy*2, tly, tmy, ly, min(ry, tmy))
+ sum_y(vx, vy*2+1, tmy+1, try_, max(ly, tmy+1), ry);
}
int sum_x(int vx, int tlx, int trx, int lx, int rx, int ly, int ry) {
if (lx > rx)
return 0;
if (lx == tlx && trx == rx)
return sum_y(vx, 1, 0, m-1, ly, ry);
int tmx = (tlx + trx) / 2;
return sum_x(vx*2, tlx, tmx, lx, min(rx, tmx), ly, ry)
+ sum_x(vx*2+1, tmx+1, trx, max(lx, tmx+1), rx, ly, ry);
}
void update_y(int vx, int lx, int rx, int vy, int ly, int ry, int x, int y, int new_val) {
if (ly == ry) {
if (lx == rx)
t[vx][vy] = new_val;
else
t[vx][vy] = t[vx*2][vy] + t[vx*2+1][vy];
} else {
int my = (ly + ry) / 2;
if (y <= my)
update_y(vx, lx, rx, vy*2, ly, my, x, y, new_val);
else
update_y(vx, lx, rx, vy*2+1, my+1, ry, x, y, new_val);
t[vx][vy] = t[vx][vy*2] + t[vx][vy*2+1];
}
}
void update_x(int vx, int lx, int rx, int x, int y, int new_val) {
if (lx != rx) {
int mx = (lx + rx) / 2;
if (x <= mx)
update_x(vx*2, lx, mx, x, y, new_val);
else
update_x(vx*2+1, mx+1, rx, x, y, new_val);
}
update_y(vx, lx, rx, 1, 0, m-1, x, y, new_val);
}
【二维】如果给出n个坐标,询问矩阵里有几个坐标,我们可以将空间优化到\(O(n \log n)\)。
具体来说,对于某个tree_x上的结点,如果它的区间是\([l,r]\),那么我们只用这个区间的点建树
【主席树】每次更新前都存一次历史版本(git-hub??)。考虑到线段树每次更新的部分很少(O(logn)长的链)所以每次只是创建一个新的链,然后把它连到上一个版本上。这样就要用到指针。
//单点更新,区间求和的可持续化线段树
struct Vertex {
Vertex *l, *r;
int sum;
Vertex(int val) : l(nullptr), r(nullptr), sum(val) {}
Vertex(Vertex *l, Vertex *r) : l(l), r(r), sum(0) {
if (l) sum += l->sum;
if (r) sum += r->sum;
}
};
Vertex* build(int a[], int tl, int tr) {
if (tl == tr)
return new Vertex(a[tl]);
int tm = (tl + tr) / 2;
return new Vertex(build(a, tl, tm), build(a, tm+1, tr));
}
int get_sum(Vertex* v, int tl, int tr, int l, int r) {
if (l > r)
return 0;
if (l == tl && tr == r)
return v->sum;
int tm = (tl + tr) / 2;
return get_sum(v->l, tl, tm, l, min(r, tm))
+ get_sum(v->r, tm+1, tr, max(l, tm+1), r);
}
Vertex* update(Vertex* v, int tl, int tr, int pos, int new_val) {
if (tl == tr)
return new Vertex(new_val);
int tm = (tl + tr) / 2;
if (pos <= tm)
return new Vertex(update(v->l, tl, tm, pos, new_val), v->r);
else
return new Vertex(v->l, update(v->r, tm+1, tr, pos, new_val));
}
【主席树】区间第k大:
? 0.将问题化简为\(0 \le a[i] \lt n\) ,知求前缀的第k大。
? 1.前缀第k大可以用权值线段树解决。于是我们可持久化地建n颗树。
2.对于a[i]取值任意的,用离散化变成\(0 \le a[i] \lt n\)
? 3.对于任意区间,我们在树上二分时将tot[x]变为第 l 颗减第 r-1 颗的即可。
\(o(nlogn)\)
暂时超出理解范围
用怪的强度作为下标,维护两个人打怪的时间,在线段树上二分找答案即可。
区间小于x的有几个数。
略
线段树合并模板题
区间更新,询问历史版本区间和,回到历史版本。
标记永久化,计算下每个标记的贡献即可。
略
询问树上两点之间的链第k大。
维护树上前缀,和树上距离公式一样处理
询问区间几个不同的树
可能有些人还不理解,放一个离线版本的。
询问x异或子树u里的某个结点的最大值
可持久化字典树,用dfs序就可以变成区间询问。
给出一棵树共nn个顶点,每个顶点有一个权值\(val_i\),你需要对每个节点统计一个最优解
每个节点的解按照一定规则产生:取出该节点的子树下所有的顶点,把顶点任意排序成一个序列,设为\(v1,v2...,vk\)
此时解为\(\sum_{i=1}^{k}\sum_{j=1}^{i}val_{v_j}\),最小的解为最优解
线段树合并, 维护前缀和的前缀和。维护每个区间前缀和的前缀和,前缀和,个数,就可以向上合并,叶子结点的计算就是等差数列求和。
初始n个空串,m个操作:
1.给[l,r]的所有字符串头尾加一个‘d’,将原字符串x变为dxd
2.求[l,r]所有字符串代表的数字之和mod 1e9+7
更新操作时可以合并的。合并两个操作,只用维护那个数字正着和倒着的大小以及长度就可以。快速更新一段区间,也只用知道区间所有数字的10的长度次的和就可以了。
给一个序列
支持3种操作
1 u v 对于所有i u<=i<=v,输出a[i]的和
2 u v t 对于所有i u<=i<=v a[i]=a[i]%t
3 u v 表示a[u]=v(将v赋值给a[u])
n,q<=1e5 a[i],t,v<=1e9
维护区间最值快速判断还有没有数字需要更新,有的话,暴力更新就好了。
标签:line 标记 有序 最优 else seq void 创建 大小
原文地址:https://www.cnblogs.com/SuuT/p/11366382.html