势能线段树
势能线段树包括了吉司机线段树。思想就是正常线段树可能遇到难 pushup 的情况,我们直接暴力递归,然后根据势能分析说明这个暴力复杂度是均摊可接受的。
【势能线段树】
【例一】
题意:
支持三种操作。
-
单点修改。
-
区间对给定数取模。
-
区间求和。
注意到一个事实:当
对线段树每个结点记录区间最大值和区间和。单点修改很好做。区间取模时,如果当前结点最大值
复杂度分析。在势能线段树的复杂度分析时,我们只需要说明 "暴力递归" 的复杂度不会太高即可。因为其他部分和普通线段树是严格
对于结点
-
当暴力递归时,说明
代表的区间内有至少一个数会折半,也就是 会减少 。所有结点的势能不增。同时所有包含被取模元素的区间势能都会减少。
-
单点修改最多只会让一条链上所有线段树结点的势能增加
,也就是总共增加 。所以最多增加 。 -
初始势能是
的。因为一层势能是 的,一共 层。
综上所述,每次递归总势能减少
typedef pair<long long, long long> pii;
pii operator+(pii a, pii b) {
return make_pair(a.first + b.first, max(a.second, b.second));
}
pii operator%(pii a, long long b) {
return make_pair(a.first % b, a.second % b);
}
struct SegmentTree {
long long sz;
vector<pair<long long, long long> > val;
pii ide1 = make_pair(0ll, 0ll);
void pushup(long long x) {
val[x] = val[x * 2] + val[x * 2 + 1];
}
void build(long long x, long long lx, long long rx, vector<long long> &a) {
if (lx + 1 == rx) {
val[x] = (lx <= a.size() ? make_pair(a[lx - 1], a[lx - 1]) : ide1);
return ;
}
long long m = (lx + rx) / 2;
build(x * 2, lx, m, a);
build(x * 2 + 1, m, rx, a);
pushup(x);
}
SegmentTree(){}
SegmentTree(vector<long long> &a) {
for (sz = 1; sz < a.size(); sz *= 2);
val.assign(2 * sz, ide1);
build(1, 1, sz + 1, a);
}
void mdf_mod(long long x, long long lx, long long rx, long long l, long long r, long long p) {
// cout << x << ' ' << lx << ' ' << rx << ' ' << l << ' ' << r << ' ' << p << endl;
if (val[x].second < p)
return ;
if (lx + 1 == rx) {
// cout << '!';
val[x] = val[x] % p;
// cout << val[x].first << ' ' << val[x].second << endl;
return ;
}
long long m = (lx + rx) / 2;
if (l < m)
mdf_mod(x * 2, lx, m, l, r, p);
if (r > m)
mdf_mod(x * 2 + 1, m, rx, l, r, p);
pushup(x);
}
void mdf(long long x, long long lx, long long rx, long long p, long long v) {
if (lx + 1 == rx) {
val[x] = make_pair(v, v);
return ;
}
long long m = (lx + rx) / 2;
if (p < m)
mdf(x * 2, lx, m, p, v);
else
mdf(x * 2 + 1, m, rx, p, v);
pushup(x);
}
pii qry(long long x, long long lx, long long rx, long long l, long long r) {
if (l <= lx && rx <= r)
return val[x];
if (l >= rx || lx >= r)
return ide1;
long long m = (lx + rx) / 2;
return qry(x * 2, lx, m, l, r) + qry(x * 2 + 1, m, rx, l, r);
}
} st;
【例二】
题意:
支持三种操作。
-
区间求最大值。
-
单点修改。
-
区间对给定数按位与。
势能线段树最重要的就是想明白什么情况可以剪枝。
显然,假设我们要对区间与上 (x | v) == v
就可以剪枝。
对结点记录区间 (valor[x] | v) == v
就退出。
复杂度分析。
一个结点的势能定义为
同时每次暴力递归总势能至少
int n, m;
int a[N];
int mx[N << 2], valor[N << 2];
void pushup(int x) {
mx[x] = max(mx[x * 2], mx[x * 2 + 1]);
valor[x] = valor[x * 2] | valor[x * 2 + 1];
}
void mdf(int x, int lx, int rx, int l, int r, int v) {
if ((v | valor[x]) == v)
return ;
if (l >= rx || lx >= r)
return ;
if (lx + 1 == rx) {
valor[x] &= v;
mx[x] = valor[x];
return ;
}
int m = (lx + rx) / 2;
mdf(x * 2, lx, m, l, r, v);
mdf(x * 2 + 1, m, rx, l, r, v);
pushup(x);
}
void mdf(int x, int lx, int rx, int p, int v) {
if (lx + 1 == rx) {
mx[x] = valor[x] = v;
return ;
}
int m = (lx + rx) / 2;
if (p < m)
mdf(x * 2, lx, m, p, v);
else
mdf(x * 2 + 1, m, rx, p, v);
pushup(x);
}
int qry(int x, int lx, int rx, int l, int r) {
if (l <= lx && rx <= r)
return mx[x];
if (l >= rx || lx >= r)
return -1;
int m = (lx + rx) / 2;
return max(qry(x * 2, lx, m, l, r), qry(x * 2 + 1, m, rx, l, r));
}
void build(int x, int lx, int rx) {
if (lx + 1 == rx) {
mx[x] = valor[x] = a[lx];
return ;
}
int m = (lx + rx) / 2;
build(x * 2, lx, m);
build(x * 2 + 1, m, rx);
pushup(x);
}
【例三】
思路:当整个区间的颜色都相同时,可以直接打懒标记区间加。
对结点
-
,若 表示区间颜色个数 ;否则表示区间颜色都是 。 -
,区间加的懒标记。我们认为 已经对 作用了,也就是只作用真子孙。 -
,区间权值和。
复杂度分析。区别在于有区间修改,需要分析会不会有势能增加。
定义结点
每次暴力递归,说明要修改的区间完全包含
-
肯定减少,从 种颜色变成赋值的颜色。 -
的子孙们势能不增。因为全部赋值成 种颜色了。 -
非
的祖先、子孙的结点势能不变,因为在线段树修改过程中都不会访问到它们。 -
剩下是
的真祖先。考虑所有 的祖先们,即线段树在正常拆区间时访问到的结点。如果粗略来看,每个被访问的结点都可能势能
,线段树每一层有 个区间被访问,一共正常访问 个结点,这么说一次操作增加 的势能,总复杂度是 吗?其实并不是,因为
内的颜色个数都 了,所以 的祖先颜色个数也 ,所以祖先的势能本就是 ,不可能增加了,反而可能减少。
综上所述,暴力递归势能必定减少。而势能初值为
【例四】
题意:
支持三个操作。
-
区间按位与。
-
区间按位或。
-
求区间最大值。
这题麻烦一些,有两个剪枝:
-
类似例一,对结点记录
valor, valand
,如果发现修改没用就直接返回。 -
如果修改对于整个区间的作用是一样的,用懒标记修改。
第一条很好理解,第二条是什么意思呢?
例如我们要与上 11000011
,如果 ??ABCD??
的形式),相当于区间整体减 ABCD00
。或是同理的。
所以我们对结点额外记录 tag, valor, valand
都 valor, valand
在这里也是可以加减的,因为我们要么是让一些 valor, valand
身上的。
具体判定:valor ^ valand
中
复杂度分析。这题涉及到势能增加。
把 valor ^ valand
中
首先强行递归肯定会使得
因此总复杂度是
void tagadd(int u, int v){
tagAnd[u] += v; tagOr[u] += v; mx[u] += v; lazy[u] += v;
}
void pushUp(int i){
tagAnd[i] = tagAnd[lc] & tagAnd[rc];
tagOr[i] = tagOr[lc] | tagOr[rc];
mx[i] = max(mx[lc], mx[rc]);
}
void pushDown(int i){
tagadd(lc, lazy[i]);
tagadd(rc, lazy[i]);
lazy[i] = 0;
}
void rangeAnd(int i,int L,int R,int l,int r,int x){
if ((tagOr[i] & x) == tagOr[i]) return;
int check = tagAnd[i]^tagOr[i]; // check 表示相等的位置。
if (l <= L && r >= R && ((check | x) == x)) // x中为0的地方,check也为0.
tagadd(i, (tagAnd[i]&x)-tagAnd[i]);
else{
int mid=(L+R)>>1;
pushDown(i);
if(l<=mid) rangeAnd(lc,L,mid,l,r,x);
if(r>mid) rangeAnd(rc,mid+1,R,l,r,x);
pushUp(i);
}
}
void rangeOr(int i,int L,int R,int l,int r,int x){
if ((tagAnd[i] | x) == tagAnd[i]) return;
int check = tagAnd[i]^tagOr[i]; // check 表示相等的位置。
if (l <= L && r >= R && ((check & x) == 0)) // x中为1的地方,check也为0.
tagadd(i, (tagAnd[i]|x)-tagAnd[i]);
else{
int mid=(L+R)>>1;
pushDown(i);
if(l<=mid) rangeOr(lc,L,mid,l,r,x);
if(r>mid) rangeOr(rc,mid+1,R,l,r,x);
pushUp(i);
}
}
int queryMax(int i,int L,int R,int l,int r){
if(l<=L&&r>=R) return mx[i];
int mid=(L+R)>>1,res=-1;
pushDown(i);
if(l<=mid) res=max(res,queryMax(lc,L,mid,l,r));
if(r>mid) res=max(res,queryMax(rc,mid+1,R,l,r));
return res;
}
void build(int i,int L,int R){
if(L==R){
int a; scanf("%d", &a);
mx[i] = tagAnd[i] = tagOr[i] = a; lazy[i]=0;
}
else{
int mid=(L+R)>>1;
build(lc,L,mid); build(rc,mid+1,R);
pushUp(i);
}
}
【例五】
支持三个操作。
-
区间求和。
-
区间加。
-
区间开方下取整。
考虑怎么剪枝。记录区间
-
若
,等价于区间减法。 -
若
,则要么等价于区间减法,要么等价于区间赋值。
对于
复杂度分析。这题的势能分析采用对数的形式,是个势能分析的 trick。
这里的定义比较特别。记
-
分析区间开方。
如果暴力递归了,说明
。我们在这个条件下求证 势能至少 。设
。转证 。当
时,显然成立。而当 时, 显然变小了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2024-02-20 数学题目合集