下面是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
代码链接: