势能线段树
什么是势能线段树
与普通线段树不同。
普通线段树需要满足区间合并和 \(Lazy\) 标记来保证时间复杂度,每次递归到底部会使复杂度退化。
而势能线段树在此基础之上选用恰当的势能,保证暴力操作的上限从而保证时间复杂度。
例题
P4145 上帝造题的七分钟 2 / 花神游历各国
题意
维护一个长度为 \(n\) 的正整数序列 \(a_i\) ,支持以下两种操作:
1 l r:将一个区间中的所有数开方,向下取整。2 l r:查询一个区间中所有数的和。
其中, \(1 \leq n,m \leq 1 \times 10^5\), \(\forall 1 \leq i \leq n\) ,都有 \(1 \leq a_i \leq 10^{12}\) 。
解法
区间开方操作不方便通过懒标记得到信息。
但是发现该题中最大的数 \(10^{12}\) 只需要执行 \(6\) 次开方操作就会变为 \(1\) ,所以一个数执行开方操作的次数的大约是 \(O(\log \log V)\) 的。
于是我们建立一颗线段树,每个结点维护最大值和区间和,如果最大值为 \(1\) 则不进行操作,否则暴力递归。
因为一个数最多执行 \(O(\log \log V ))\) 次开方操作,所以总复杂度是\(O(n \log n \log \log V + q \log n)\),可以通过本题。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+100;
int n,m;ll a[N];
struct Segment{
#define ls rt*2
#define rs rt*2+1
struct node{
ll tag,mx,mn,sum;
}t[N*4];
void pushtag(int rt,int l,int r,ll k){
t[rt].mn-=k,t[rt].mx-=k;
t[rt].tag+=k,t[rt].sum-=(r-l+1)*k;
}
void pushdown(int rt,int l,int r){
if(t[rt].tag!=0){
int mid=(l+r)/2;
pushtag(ls,l,mid,t[rt].tag);
pushtag(rs,mid+1,r,t[rt].tag);
t[rt].tag=0;
}
}
void pushup(int rt,int l,int r){
t[rt].mx=max(t[ls].mx,t[rs].mx);
t[rt].mn=min(t[ls].mn,t[rs].mn);
t[rt].sum=t[ls].sum+t[rs].sum;
}
void update(int rt,int l,int r,int L,int R){
if(L<=l&&R>=r&&t[rt].mx==t[rt].mn){
pushtag(rt,l,r,t[rt].mx-(ll)sqrt(1.0*t[rt].mx));
return;
}
pushdown(rt,l,r);
int mid=(l+r)/2;
if(L<=mid)update(ls,l,mid,L,R);
if(R>mid)update(rs,mid+1,r,L,R);
pushup(rt,l,r);
}
ll query(int rt,int l,int r,int L,int R){
if(L<=l&&R>=r)return t[rt].sum;
pushdown(rt,l,r);
int mid=(l+r)/2;
if(L<=mid&&R>mid)return query(ls,l,mid,L,R)+query(rs,mid+1,r,L,R);
if(L<=mid)return query(ls,l,mid,L,R);
return query(rs,mid+1,r,L,R);
}
void build(int rt,int l,int r){
if(l==r){
t[rt]={0,a[l],a[l],a[l]};
return;
}
int mid=(l+r)/2;
build(ls,l,mid);build(rs,mid+1,r);
pushup(rt,l,r);
}
}t;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
t.build(1,1,n);
scanf("%d",&m);
while(m--){
int op,l,r;
scanf("%d %d %d",&op,&l,&r);
if(l>r)swap(l,r);
if(op==0){
t.update(1,1,n,l,r);
}else{
printf("%lld\n",t.query(1,1,n,l,r));
}
}
}
AT_abc256_h [ABC256Ex] I like Query Problem
题意
给定 \(N\)、\(Q\) 以及 \(A = (a_1, a_2, \dots, a_N)\)。
请处理 \(Q\) 个如下所述的查询。查询有以下三种类型之一:
1 L R x:将 \(i = L, L+1, \dots, R\) 的 \(a_i\) 更新为 \(\left\lfloor \frac{a_i}{x} \right\rfloor\)。2 L R y:将 \(i = L, L+1, \dots, R\) 的 \(a_i\) 更新为 \(y\)。3 L R:输出 \(\sum_{i=L}^R a_i\)。
其中, \(1 \leq N \leq 5 \times 10^5\) ,\(1 \leq Q \leq 1 \times 10^5\) ,输入的所有数均为在 \([1,10^5]\) 之间。
解法
普通线段树不好做整除操作,但是如果一个区间中的数全部相同,那么可以直接当成区间减法 。
注意到如果 \(x\) 是 \(1\) ,整个区间不变,否则区间内的数至少变为原来的一半,所以一个区间最多整除 \(O(n \log(n))\) 次区间中的数就会全部相同。
有了这个发现,就可以建线段树,每个结点记录最大值和最小值,如果最大值等于最小值,就打区间减法标记。
虽然这样复杂度是正确的,但是我们还可以优化。
有这样一种情况,一个区间中只有两种数,设为 \(a\) 和 \(b\) ,如果\(a + 1 = b\) 并且 \(a \bmod x=0\) 那么也可以当做减法来做,比如这个区间中只有 \(5\) 和 \(6\) 两种数,除以 \(3\) 向下取整,就会得到 \(1\) 和 \(2\) ,相当于区间减了 \(4\) 。
这样总复杂度是 \(O(n \log n \log V + q \log n \log V)\) ,可以通过本题。
P10639 BZOJ4695 最假女选手
题意
给定一个长度为 \(n\) 的序列,要求支持以下 \(6\) 种操作:
- 给一个区间 \([l,r]\) 加上一个整数 \(x\);
- 把一个区间 \([l,r]\) 内小于 \(x\) 的数都变成 \(x\);
- 把一个区间 \([l,r]\) 内大于 \(x\) 的数都变成 \(x\);
- 求区间 \([l,r]\) 的和;
- 求区间 \([l,r]\) 的最大值;
- 求区间 \([l,r]\) 的最小值;
数据保证,\(1\leq n,m\leq 5\times 10^5\),\(|a_i|\leq 10^8\)。
当进行 \(1\) 操作时,\(|x| \leq 1000\);
当进行 \(2,3\) 操作时,\(|x| \leq 10^8\)。
解法
发现除了操作 \(2,3\) 其他操作都可以使用普通线段树完成,复杂度为 \(O(n \log n + q \log n)\) 。
现在考虑操作 \(2\) 。操作 \(2\) 其实是对一段区间对 \(x\) 取 \(\max\) 发现这个东西不是很好用普通线段树维护。
我们对每一个结点维护区间最小值、次小值和最小值个数。
设最小值为 \(mn\) ,次小值为 \(sec\) 。
现在分 \(3\) 种情况讨论:
- 如果 \(x \leq mn\) ,没有影响,直接返回
- 如果 \(mn \lt x \lt sec\) ,显然,这个操作只会影响这个区间中的最小值,在这个节点上打一个最小值加 \(x - mn\) 的标记。
- 如果 \(x \geq sec\) ,暴力递归左右儿子,直到变为前两种情况。
现在来说明为什么这么做复杂度是对的。
定义线段树上每一个点的势能函数 \(\varphi(u)\) 为 \([min(u) \neq min(fa)]\) 。当向下暴力递归时,一定至少有 \(1\) 个儿子的 \(\varphi(u)\) 会减少\(1\) ,所以 \(1\) 的势能最多带来 \(O(\log n)\) 的时间复杂度(最坏情况递归一条链)。
初始时势能是 \(O(n)\) 的,每一次区间修改会影响 \(O(\log n)\) 个点。
所以总时间复杂度就是 \(O(n \log n + q \log ^ 2 n)\) 的。
代码
#include<bits/stdc++.h>
#define mid (l+r)/2
#define ls rt*2
#define rs ls+1
#define lson ls,l,mid
#define rson rs,mid+1,r
typedef long long ll;
using namespace std;
const int N=5e5+100,inf=INT_MAX;
int a[N],n,m;
struct tree{
int mx1,mx2,mxcnt,mn1,mn2,mncnt,tag1,tag2,tag3;ll sum;
}t[N*8];
void pushtag1(int rt,int l,int r,int x){
t[rt].tag1+=x,t[rt].mx1+=x,t[rt].mn1+=x,t[rt].sum+=(ll)(r-l+1)*x;
if(t[rt].mx2!=-inf)t[rt].mx2+=x;
if(t[rt].mn2!=inf)t[rt].mn2+=x;
if(t[rt].tag2!=-inf)t[rt].tag2+=x;
if(t[rt].tag3!=inf)t[rt].tag3+=x;
}
void pushtag2(int rt,int l,int r,int x){
if(t[rt].mn1>=x)return;
t[rt].sum+=(ll)(x-t[rt].mn1)*t[rt].mncnt;
if(t[rt].mn1==t[rt].mx1)t[rt].mx1=x;
if(t[rt].mn1==t[rt].mx2)t[rt].mx2=x;
if(t[rt].tag3<x)t[rt].tag3=x;
t[rt].mn1=x,t[rt].tag2=x;
}
void pushtag3(int rt,int l,int r,int x){
if(t[rt].mx1<=x)return;
t[rt].sum+=(ll)(x-t[rt].mx1)*t[rt].mxcnt;
if(t[rt].mx1==t[rt].mn1)t[rt].mn1=x;
if(t[rt].mx1==t[rt].mn2)t[rt].mn2=x;
if(t[rt].tag2>x)t[rt].tag2=x;
t[rt].mx1=x,t[rt].tag3=x;
}
void pushdown(int rt,int l,int r){
if(t[rt].tag1){
pushtag1(lson,t[rt].tag1);
pushtag1(rson,t[rt].tag1);
t[rt].tag1=0;
}
if(t[rt].tag2!=-inf){
pushtag2(lson,t[rt].tag2);
pushtag2(rson,t[rt].tag2);
t[rt].tag2=-inf;
}
if(t[rt].tag3!=inf){
pushtag3(lson,t[rt].tag3);
pushtag3(rson,t[rt].tag3);
t[rt].tag3=inf;
}
}
void pushup(int rt,int l,int r){
t[rt].sum=t[ls].sum+t[rs].sum;
t[rt].mx1=max(t[ls].mx1,t[rs].mx1);
t[rt].mx2=(t[ls].mx1==t[rs].mx1)?max(t[ls].mx2,t[rs].mx2):
(t[ls].mx1>t[rs].mx1)?max(t[rs].mx1,t[ls].mx2):max(t[ls].mx1,t[rs].mx2);
t[rt].mxcnt=(t[ls].mx1==t[rs].mx1)?t[ls].mxcnt+t[rs].mxcnt:(t[ls].mx1>t[rs].mx1)?t[ls].mxcnt:t[rs].mxcnt;
t[rt].mn1=min(t[ls].mn1,t[rs].mn1);
t[rt].mn2=(t[ls].mn1==t[rs].mn1)?min(t[ls].mn2,t[rs].mn2):
(t[ls].mn1<t[rs].mn1)?min(t[rs].mn1,t[ls].mn2):min(t[ls].mn1,t[rs].mn2);
t[rt].mncnt=(t[ls].mn1==t[rs].mn1)?t[ls].mncnt+t[rs].mncnt:(t[ls].mn1<t[rs].mn1)?t[ls].mncnt:t[rs].mncnt;
}
void update1(int rt,int l,int r,int L,int R,int x){
if(L<=l&&R>=r){
pushtag1(rt,l,r,x);
return;
}
pushdown(rt,l,r);
if(L<=mid)update1(lson,L,R,x);
if(R>mid)update1(rson,L,R,x);
pushup(rt,l,r);
}
void update2(int rt,int l,int r,int L,int R,int x){
if(t[rt].mn1>=x)return;
if(L<=l&&R>=r&&t[rt].mn2>x){
pushtag2(rt,l,r,x);
return;
}
pushdown(rt,l,r);
if(L<=mid)update2(lson,L,R,x);
if(R>mid)update2(rson,L,R,x);
pushup(rt,l,r);
}
void update3(int rt,int l,int r,int L,int R,int x){
if(t[rt].mx1<=x)return;
if(L<=l&&R>=r&&t[rt].mx2<x){
pushtag3(rt,l,r,x);
return;
}
pushdown(rt,l,r);
if(L<=mid)update3(lson,L,R,x);
if(R>mid)update3(rson,L,R,x);
pushup(rt,l,r);
}
ll query1(int rt,int l,int r,int L,int R){
if(L<=l&&R>=r)return t[rt].sum;
pushdown(rt,l,r);
ll ans=0;
if(L<=mid)ans+=query1(lson,L,R);
if(R>mid)ans+=query1(rson,L,R);
return ans;
}
ll query2(int rt,int l,int r,int L,int R){
if(L<=l&&R>=r)return t[rt].mx1;
pushdown(rt,l,r);
ll ans=-inf;
if(L<=mid)ans=max(ans,query2(lson,L,R));
if(R>mid)ans=max(ans,query2(rson,L,R));
return ans;
}
ll query3(int rt,int l,int r,int L,int R){
if(L<=l&&R>=r)return t[rt].mn1;
pushdown(rt,l,r);
ll ans=inf;
if(L<=mid)ans=min(ans,query3(lson,L,R));
if(R>mid)ans=min(ans,query3(rson,L,R));
return ans;
}
void build(int rt,int l,int r){
t[rt].tag2=-inf,t[rt].tag3=inf;
if(l==r){
t[rt]={a[l],-inf,1,a[l],inf,1,0,-inf,inf,a[l]};
return;
}
build(lson),build(rson);
pushup(rt,l,r);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
build(1,1,n);
scanf("%d",&m);
while(m--){
int op,l,r,x;
scanf("%d %d %d",&op,&l,&r);
if(op<=3)scanf("%d",&x);
if(op==1)update1(1,1,n,l,r,x);
if(op==2)update2(1,1,n,l,r,x);
if(op==3)update3(1,1,n,l,r,x);
if(op==4)printf("%lld\n",query1(1,1,n,l,r));
if(op==5)printf("%lld\n",query2(1,1,n,l,r));
if(op==6)printf("%lld\n",query3(1,1,n,l,r));
}
}
P5693 EI 的第六分块
题意
给定一个整数序列 \(a\),支持两种操作:
1 l r x表示给区间 \([l,r]\) 中每个数加上 \(x\)2 l r表示查询区间 \([l,r]\) 的最大子段和(可以为空)
其中, \(1\le n,q \le 4\times 10^5\),\(|a_i| \le 10^9\),\(1 \le x \le 10^6\)。
解法
如果这个题是单点修改,那么是好做的。
每个结点维护区间和 \(sum\) ,最大前缀和 \(lmax\) ,最大后缀和 \(rmax\) ,最大字段和 \(max\) 。
区间合并时,就有
\(sum = ls.sum + rs.sum\)
\(lmax = \max(ls.lmax , ls.sum + rs.lmax)\)
\(rmax = \max(rs.rmax , rs.sum + rs.rmax)\)
\(max = \max(ls.max , rs.max , ls.rmax + rs.lmax)\)
这一部分是简单的。
现在考虑加上区间加操作,注意到这个题加的 \(x\) 都是正数,所以 \(lmax , rmax , max\) 的长度都不会缩短。
在实现的时候 \(lmax , rmax , max\) 都用一个二元组 \((k,b)\) 来表示,\(b\) 表示现在的值, \(k\) 就表示区间的长度。
在修改的时候,如果这些的区间长度不变,那么可以直接用 \(y = kx + b\) 来得到新的 \(b\) 。
现在的问题是\(lmax , rmax , max\)的区间长度我们不知道是否变化,以及要变化多少。
考虑对每一个结点维护一个阈值 \(x\) ,代表如果加的数 \(\geq x\) , \(lmax , rmax , max\) 中至少有一个的长度会发生变化。
递归时如果这个点的区间都不会发生变化,那么直接打懒标记,否则暴力递归,知道区间长度不发生改变为止。
阈值 \(x\) 合并的时候根据继承的情况取个 \(\min\) 即可。
总复杂度为 \(O( ( n + m ) \log ^ 3 n + q \log n)\) ,其中 \(n\) 是序列长度, \(m\) 是修改次数, \(q\) 是查询次数。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e5+100;
const ll inf=1e16;
int a[N],n,q;
struct func{
int k;ll b;
func operator+(const func &a)const{return {k+a.k,b+a.b};}
void add(ll w){b+=k*w;}
};
pair<func,ll> max(func a,func b){
if(a.k<b.k||(a.k==b.k&&a.b<b.b))swap(a,b);
if(a.b>=b.b)return {a,inf};
return {b,(b.b-a.b)/(a.k-b.k)};
}
struct tree{
func lmax,rmax,tot,sum;ll x;
tree operator+(const tree &a)const{
tree res;
pair<func,ll> tmp;
res.x=min(x,a.x);
tmp=max(lmax,sum+a.lmax);
res.lmax=tmp.first,res.x=min(res.x,tmp.second);
tmp=max(a.rmax,a.sum+rmax);
res.rmax=tmp.first,res.x=min(res.x,tmp.second);
tmp=max(tot,a.tot);
res.x=min(res.x,tmp.second);
tmp=max(tmp.first,rmax+a.lmax);
res.tot=tmp.first,res.x=min(res.x,tmp.second);
res.sum=sum+a.sum;
return res;
}
};
struct KTT{
#define ls rt*2
#define rs ls+1
#define mid (l+r)/2
#define lson ls,l,mid
#define rson rs,mid+1,r
struct node{
tree a;ll tag;
}t[N*4];
void pushup(int rt,int l,int r){
t[rt].a=t[ls].a+t[rs].a;
}
void pushtag(int rt,int l,int r,ll v){
t[rt].tag+=v;
t[rt].a.x-=v;
t[rt].a.lmax.add(v);
t[rt].a.rmax.add(v);
t[rt].a.tot.add(v);
t[rt].a.sum.add(v);
}
void pushdown(int rt,int l,int r){
if(t[rt].tag){
pushtag(lson,t[rt].tag);
pushtag(rson,t[rt].tag);
t[rt].tag=0;
}
}
void build(int rt,int l,int r){
if(l==r){
func q={1,a[l]};
t[rt].a={q,q,q,q,inf};
return;
}
build(lson);build(rson);
pushup(rt,l,r);
}
void defeat(int rt,int l,int r,ll v){
if(v>t[rt].a.x){
ll tt=t[rt].tag+v;
t[rt].tag=0;
defeat(lson,tt);
defeat(rson,tt);
pushup(rt,l,r);
return;
}
pushtag(rt,l,r,v);
}
void update(int rt,int l,int r,int L,int R,ll v){
if(L<=l&&R>=r){
defeat(rt,l,r,v);
return;
}
pushdown(rt,l,r);
if(L<=mid)update(lson,L,R,v);
if(R>mid)update(rson,L,R,v);
pushup(rt,l,r);
}
tree query(int rt,int l,int r,int L,int R){
if(L<=l&&R>=r)return t[rt].a;
pushdown(rt,l,r);
if(L<=mid&&R>mid)return query(lson,L,R)+query(rson,L,R);
if(L<=mid)return query(lson,L,R);
return query(rson,L,R);
}
}t;
int main(){
scanf("%d %d",&n,&q);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
t.build(1,1,n);
while(q--){
int op,l,r,x;
scanf("%d %d %d",&op,&l,&r);
if(op==1){
scanf("%d",&x);
t.update(1,1,n,l,r,x);
}else{
printf("%lld\n",max(0ll,t.query(1,1,n,l,r).tot.b));
}
}
}

浙公网安备 33010602011771号