势能线段树
线段树能够通过打懒标记实现区间修改的条件有两个:
- 能够快速处理懒标记对区间询问结果的影响
- 能够快速实现懒标记的合并
但有的区间修改不满足上面两个条件(如区间整除/开方/取模等)。
但某些修改存在一些奇妙的性质,使得序列每个元素被修改的次数有一个上限。
所以可以在线段树每个节点上记录一个值,表示对应区间内是否每个元素都达到修改次数上限。区间修改时暴力递归到叶子节点,如果途中遇到一个节点,这个节点的对应区间内每个元素都达到修改次数上限则在这个节点returnreturnreturn掉。
可以证明复杂度为 O((n+mlogn)×lim)O((n+m\log n)\times lim)O((n+mlogn)×lim),其中nnn为序列长度,mmm为询问次数,limlimlim为修改次数上限。
LOJ6029——线段树区间除法
对于每个区间维护区间内的 最大值MaxMaxMax 和 最小值MinMinMin,对于整除操作,如果有Max−⌊Maxd⌋=Min−⌊Mind⌋Max-\lfloor\frac{Max}{d}\rfloor = Min − \lfloor\frac{Min}{d}\rfloorMax−⌊dMax⌋=Min−⌊dMin⌋,就转化为区间减。否则直接向下递归。
考虑何时满足Max−⌊Maxd⌋=Min−⌊Mind⌋Max-\lfloor\frac{Max}{d}\rfloor = Min − \lfloor\frac{Min}{d}\rfloorMax−⌊dMax⌋=Min−⌊dMin⌋:
设Max=k1d+c1,Min=k2d+c2(c1,c2∈[0,d−1],d≥2)Max=k_1d+c_1,Min=k_2d+c_2(c_1,c_2\in[0,d-1],d\geq 2)Max=k1d+c1,Min=k2d+c2(c1,c2∈[0,d−1],d≥2)
Max−⌊Maxd⌋=Min−⌊Mind⌋Max-\lfloor\frac{Max}{d}\rfloor=Min-\lfloor\frac{Min}{d}\rfloorMax−⌊dMax⌋=Min−⌊dMin⌋
Max−Min=⌊Maxd⌋−⌊Mind⌋Max-Min=\lfloor\frac{Max}{d}\rfloor-\lfloor\frac{Min}{d}\rfloorMax−Min=⌊dMax⌋−⌊dMin⌋
(k1−k2)d+c1−c2=k1−k2(k_1-k_2)d+c_1-c_2=k_1-k_2(k1−k2)d+c1−c2=k1−k2
(k1−k2)(d−1)=c2−c1(k_1-k_2)(d-1)=c_2-c_1(k1−k2)(d−1)=c2−c1
若k1=k2,则c2=c1,所以Max=Min若k_1=k_2,则c_2=c_1,所以Max=Min若k1=k2,则c2=c1,所以Max=Min
若k1>k2,则(k1−k2)(d−1)≥d−1,又因为c2−c1≤d−1,所以当且仅当Max=k1d,Min=(k1−1)d+d−1时成立若k_1>k_2,则(k_1-k_2)(d-1)\geq d-1,又因为c_2-c_1\leq d-1,所以当且仅当Max=k_1d,Min=(k_1-1)d+d-1时成立若k1>k2,则(k1−k2)(d−1)≥d−1,又因为c2−c1≤d−1,所以当且仅当Max=k1d,Min=(k1−1)d+d−1时成立
所以一个区间最多暴力向下递归log2(Max−Min)\log_2(Max-Min)log2(Max−Min)次。记势能函数ϕ(T)\phi(T)ϕ(T)为线段树TTT每一个节点log(Max−Min)\log(Max−Min)log(Max−Min)的和,每一次修改操作会影响O(logn)O(\log n)O(logn)个节点的Max−MinMax-MinMax−Min值,让ϕ(T)\phi(T)ϕ(T)增加lognlogA\log n\log AlognlogA,其中AAA为值域。因此总时间复杂度为O(nlogA+mlognlogA)O(n\log A+m\log n\log A)O(nlogA+mlognlogA)。
UOJ228——线段树区间开根
同上,对于每个区间维护区间内的 最大值MaxMaxMax 和 最小值MinMinMin,对于开根操作,如果有Max−⌊Max⌋=Min−⌊Min⌋Max-\lfloor\sqrt{Max}\rfloor = Min − \lfloor\sqrt{Min}\rfloorMax−⌊Max⌋=Min−⌊Min⌋,就转化为区间减。否则直接向下递归。
考虑何时满足Max−⌊Max⌋=Min−⌊Min⌋Max-\lfloor\sqrt{Max}\rfloor = Min − \lfloor\sqrt{Min}\rfloorMax−⌊Max⌋=Min−⌊Min⌋:
设Max=k12+c1,Min=k22+c2(c1∈[0,2k1];c2∈[0,2k2];k1,k2≥1)Max=k_1^2+c_1,Min=k_2^2+c_2(c_1\in[0,2k_1];c_2\in[0,2k_2];k_1,k_2\geq 1)Max=k12+c1,Min=k22+c2(c1∈[0,2k1];c2∈[0,2k2];k1,k2≥1)
Max−⌊Max⌋=Min−⌊Min⌋Max-\lfloor\sqrt{Max}\rfloor = Min − \lfloor\sqrt{Min}\rfloorMax−⌊Max⌋=Min−⌊Min⌋
k12+c1−k1=k22+c2−k2k_1^2+c_1-k_1=k_2^2+c_2-k_2k12+c1−k1=k22+c2−k2
k12−k22−(k1−k2)=c2−c1k_1^2-k_2^2-(k_1-k_2)=c_2-c_1k12−k22−(k1−k2)=c2−c1
(k1+k2−1)(k1−k2)=c2−c1(k_1+k_2-1)(k_1-k_2)=c_2-c_1(k1+k2−1)(k1−k2)=c2−c1
若k1=k2,则c2=c1,所以Max=Min若k_1=k_2,则c_2=c_1,所以Max=Min若k1=k2,则c2=c1,所以Max=Min
若k1>k2,则(k1+k2−1)(k1−k2)≥k1+k2−1≥2k2,又因为c2−c1≤2k2,所以当且仅当Max=k12,Min=(k1−1)2+2(k1−1)=k12−1时成立若k_1>k_2,则(k_1+k_2-1)(k_1-k_2)\geq k_1+k_2-1\geq 2k_2,又因为c_2-c_1\leq 2k_2,所以当且仅当Max=k_1^2,Min=(k_1-1)^2+2(k_1-1)=k_1^2-1时成立若k1>k2,则(k1+k2−1)(k1−k2)≥k1+k2−1≥2k2,又因为c2−c1≤2k2,所以当且仅当Max=k12,Min=(k1−1)2+2(k1−1)=k12−1时成立
开根后,区间极差由Max−MinMax-MinMax−Min变为Max−Min\sqrt{Max}-\sqrt{Min}Max−Min,又由Max−MinMax−Min=Max+Min>Max−Min\frac{Max-Min}{\sqrt{Max}-\sqrt{Min}}=\sqrt{Max}+\sqrt{Min}>\sqrt{Max}-\sqrt{Min}Max−MinMax−Min=Max+Min>Max−Min知Max−Min>Max−Min\sqrt{Max-Min}>\sqrt{Max}-\sqrt{Min}Max−Min>Max−Min。
所以一个区间最多暴力向下递归 loglog(Max−Min)\log\log(Max−Min)loglog(Max−Min) 次,总时间复杂度为O(nloglogA+mlognloglogA)O(n\log\log A+m\log n\log\log A)O(nloglogA+mlognloglogA),其中AAA为值域。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int inf=1e9+7;
const int N=1e5+10;
ll mx[N<<2],mn[N<<2],sum[N<<2],tag[N<<2],a[N];
int n,q;
ll Sqr(ll x){return sqrt(x);
}
void build(int u,int l,int r){tag[u]=0;if(l==r){mx[u]=mn[u]=sum[u]=a[l];return;}int mid=(l+r)>>1;build(u<<1,l,mid);build(u<<1|1,mid+1,r);mx[u]=max(mx[u<<1],mx[u<<1|1]);mn[u]=min(mn[u<<1],mn[u<<1|1]);sum[u]=sum[u<<1]+sum[u<<1|1];
}
void pushdown(int u,int l,int r){if(tag[u]!=0){int mid=(l+r)>>1;mx[u<<1]+=tag[u];mn[u<<1]+=tag[u];sum[u<<1]+=tag[u]*(mid-l+1);tag[u<<1]+=tag[u];mx[u<<1|1]+=tag[u];mn[u<<1|1]+=tag[u];sum[u<<1|1]+=tag[u]*(r-mid);tag[u<<1|1]+=tag[u];tag[u]=0;}
}
void add(int u,int l,int r,int a,int b,ll x){if(a<=l&&r<=b){mx[u]+=x;mn[u]+=x;sum[u]+=x*(r-l+1);tag[u]+=x;return;}pushdown(u,l,r);int mid=(l+r)>>1;if(a<=mid) add(u<<1,l,mid,a,b,x);if(b>mid) add(u<<1|1,mid+1,r,a,b,x);mx[u]=max(mx[u<<1],mx[u<<1|1]);mn[u]=min(mn[u<<1],mn[u<<1|1]);sum[u]=sum[u<<1]+sum[u<<1|1];
}
void sqr(int u,int l,int r,int a,int b){if(a<=l&&r<=b&&mx[u]-Sqr(mx[u])==mn[u]-Sqr(mn[u])){ll x=mx[u]-Sqr(mx[u]);mx[u]-=x;mn[u]-=x;sum[u]-=x*(r-l+1);tag[u]-=x;return;}pushdown(u,l,r);int mid=(l+r)>>1;if(a<=mid) sqr(u<<1,l,mid,a,b);if(b>mid) sqr(u<<1|1,mid+1,r,a,b);mx[u]=max(mx[u<<1],mx[u<<1|1]);mn[u]=min(mn[u<<1],mn[u<<1|1]);sum[u]=sum[u<<1]+sum[u<<1|1];
}
ll Sum(int u,int l,int r,int a,int b){if(a<=l&&r<=b) return sum[u];pushdown(u,l,r);int mid=(l+r)>>1;ll res=0;if(a<=mid) res=res+Sum(u<<1,l,mid,a,b);if(b>mid) res=res+Sum(u<<1|1,mid+1,r,a,b);return res;
}
int main(){scanf("%d%d",&n,&q);for(int i=1;i<=n;i++) scanf("%lld",&a[i]);build(1,1,n);int opt,l,r;ll x;while(q--){scanf("%d%d%d",&opt,&l,&r);if(opt==1){scanf("%lld",&x);add(1,1,n,l,r,x);}else if(opt==2){sqr(1,1,n,l,r);}else if(opt==3){printf("%lld\n",Sum(1,1,n,l,r));}}return 0;
}
CF438D——线段树区间取模
对于每个区间维护区间内的 最大值MaxMaxMax,对于整除操作,如果有Max<modMax<modMax<mod,就直接忽略。否则暴力向下递归。
可以证明,对于一个数xxx,最多取模logx\log xlogx次就会令其变为1:
- 若mod>x2mod>\frac{x}{2}mod>2x,那么x%mod=x−mod<x2x\%mod=x−mod<\frac{x}{2}x%mod=x−mod<2x
- 若mod=x2mod=\frac{x}{2}mod=2x,那么x%mod=0x\%mod=0x%mod=0
- 若mod<x2mod<\frac{x}{2}mod<2x,那么x%mod<mod<x2x\%mod<mod<\frac{x}{2}x%mod<mod<2x
总时间复杂度为(nlogA+mlognlogA)(n\log A+m\log n\log A)(nlogA+mlognlogA),其中AAA为值域。
CF920F——线段树区间取约数个数
设D(x)D(x)D(x)表示xxx的约数个数。
发现D(x)D(x)D(x)有以下性质:
- 若x≤2x\leq 2x≤2,D(x)=xD(x)=xD(x)=x
- D(x)≤2xD(x)\leq 2\sqrt{x}D(x)≤2x
那么一个数xxx的修改次数上限为loglogx\log\log xloglogx。
套用上面的方法即可。