下面是OI Wiki对吉司机线段树(势能线段树的一种)和李超线段树的详解:
吉司机线段树
李超线段树
在信息学中,势能被用于计算某一个过程,或者某一类过程时间复杂度的总和。
我们知道,线段树能够通过打标记实现区间修改的条件有两个:
1.能够快速处理标记对区间询问结果的影响
2.能够快速实现标记的合并
但有的区间修改不满足上面两个条件(如区间开方,区间取模等)
但某些修改存在一些奇妙的性质,使得序列每个元素被修改的次数有一个上限
可以在线段树每个节点上记录一个值,表示对应区间内是否每个元素都达到修改次数上限
区间修改时暴力递归到叶子节点,如果途中遇到一个节点,这个节点的对应区间内每个元素都达到修改次数上限则在这个节点 return 掉
引用:https://blog.csdn.net/Daniel__d/article/details/105104831
A 智乃的直线
代码链接:
B 弩蚊怒夏
代码链接:
C 势能线段树模板题二
带修改的区间开方
我们发现对于一个序列,如果开方的前后,最大值的变化量等于最小值的变化量,那么其实这就是一个区间减的操作(因为显然这个变化量是有单调性的),因此我们只需要维护区间最大值和区间最大值即可,然后暴力修改,遇到满足条件的就区间减,然后直接return
代码链接:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=48707568
int n,m;
ll a[N];
struct node{
int l,r;
ll lazy;
ll sum,maxx,minn;
}tree[N<<2];
void pushup(int u){
tree[u].sum=tree[u<<1].sum+tree[u<<1|1].sum;
tree[u].maxx=max(tree[u<<1].maxx,tree[u<<1|1].maxx);
tree[u].minn=min(tree[u<<1].minn,tree[u<<1|1].minn);
}
void addlazy(int u,ll v){
tree[u].lazy+=v;
tree[u].sum+=v*(tree[u].r-tree[u].l+1);
tree[u].maxx+=v;
tree[u].minn+=v;
}
void pushdown(int u){
if(tree[u].lazy){
addlazy(u<<1,tree[u].lazy);
addlazy(u<<1|1,tree[u].lazy);
tree[u].lazy=0;
}
}
void build(int u,int l,int r){
tree[u].l=l,tree[u].r=r;
tree[u].minn=INF;tree[u].maxx=-INF;
if(l==r){
tree[u].sum=tree[u].maxx=tree[u].minn=a[l];
return;
}
int mid=(l+r)>>1;
build(u<<1,l,mid);build(u<<1|1,mid+1,r);
pushup(u);
}
void modify(int u,int l,int r){
if(l<=tree[u].l&&tree[u].r<=r){
ll fx=tree[u].maxx-(ll)sqrt(tree[u].maxx);
ll fy=tree[u].minn-(ll)sqrt(tree[u].minn);
if(fx==fy) return addlazy(u,-fx);
}
pushdown(u);
int mid=(tree[u].l+tree[u].r)>>1;
if(r<=mid) modify(u<<1,l,r);
else if(l>mid) modify(u<<1|1,l,r);
else modify(u<<1,l,r),modify(u<<1|1,l,r);
pushup(u);
}
ll query(int u,int l,int r){
if(l<=tree[u].l&&tree[u].r<=r) return tree[u].sum;
pushdown(u);
int mid=(tree[u].l+tree[u].r)>>1;
if(r<=mid) return query(u<<1,l,r);
else if(l>mid) return query(u<<1|1,l,r);
else return query(u<<1,l,r)+query(u<<1|1,l,r);
}
void add(int u,int l,int r,ll v){
if(l<=tree[u].l&&tree[u].r<=r){
addlazy(u,v);
return;
}
pushdown(u);
int mid=(tree[u].l+tree[u].r)>>1;
if(r<=mid) add(u<<1,l,r,v);
else if(l>mid) add(u<<1|1,l,r,v);
else add(u<<1,l,r,v),add(u<<1|1,l,r,v);
pushup(u);
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
while(m--){
int op;cin>>op;
if(op==1){
int l,r;cin>>l>>r;
modify(1,l,r);
}
else if(op==2){
int l,r;ll v;cin>>l>>r>>v;
add(1,l,r,v);
}
else{
int l,r;cin>>l>>r;
cout<<query(1,l,r)<<"\n";
}
}
}D 势能线段树模板题一
区间开方
我们可以发现对 1 或 0 的开方,对他的值是不会改变的
所以当遇到 1 或 0 可以提前return掉
即题目中的定义区间最大值max为势能,势能初始值为区间最大值,定义0势能点为max=1
代码链接:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=48704791
int n,m;
ll a[N];
struct node{
int l,r;
ll sum,maxx;
}tree[N<<2];
void pushup(int u){
tree[u].sum=tree[u<<1].sum+tree[u<<1|1].sum;
tree[u].maxx=max(tree[u<<1].maxx,tree[u<<1|1].maxx);
}
void build(int u,int l,int r){
tree[u].l=l,tree[u].r=r;
if(l==r){
tree[u].sum=tree[u].maxx=a[l];
return;
}
int mid=(l+r)>>1;
build(u<<1,l,mid);build(u<<1|1,mid+1,r);
pushup(u);
}
void modify(int u,int l,int r){
if(tree[u].maxx<=1) return;
if(tree[u].l==tree[u].r){
tree[u].sum=tree[u].maxx=sqrt(tree[u].maxx);
return;
}
int mid=(tree[u].l+tree[u].r)>>1;
if(r<=mid) modify(u<<1,l,r);
else if(l>mid) modify(u<<1|1,l,r);
else modify(u<<1,l,r),modify(u<<1|1,l,r);
pushup(u);
}
ll query(int u,int l,int r){
if(l<=tree[u].l&&tree[u].r<=r) return tree[u].sum;
int mid=(tree[u].l+tree[u].r)>>1;
if(r<=mid) return query(u<<1,l,r);
else if(l>mid) return query(u<<1|1,l,r);
else return query(u<<1,l,r)+query(u<<1|1,l,r);
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,1,n);
while(m--){
int op,l,r;cin>>op>>l>>r;
if(op==1) modify(1,l,r);
else cout<<query(1,l,r)<<"\n";
}
}E [HEOI2013]SEGMENT
代码链接:

京公网安备 11010502036488号