【算法学习-势能线段树】
势能线段树
势能均摊复杂度
- 在N个数求gcd问题中,总时间复杂度为O(N+logC)
- 那么势能均摊复杂度就为O(N+logC)/N = O(1+logC/N) = O(1)
- 也就是对每个数来说gcd的操作的时间复杂度是O(1)的
- 注意:可持久化数据结构由于其O(1)版本记录的特性,显然会使得操作被重现,当然,不止是使用可持久化数据结构时候要注意,任何可重置函数调用状态的操作在均摊算法中都是应该被警惕的。
例题:
区间开方,区间加减,区间查询
-
与加减操作不同的是,区间开方不支持使用lazy。(因为区间内的数字不同,开方也不同)
-
线段树中数字经过多次开平方就变成1(1e9的数字仅需要6次)
-
所以如果区间内的数不同(max!=min)就暴力开方,直到区间所有数都相同,再借用lazy(这个是lazy是区间置数set)
-
最坏情况需要暴力开方logN个节点,每次开方logN,m次操作,所以操作的时间复杂度就是\(O(Mlogn^2)\)
int a[N];·
struct SegTree{
ll sum,lazy,set,max,min;
}tree[N<<2];
void down_lazy(int rt,int ch){//下传懒标记
if(tree[rt].lazy){
if(tree[ch].set){
tree[ch].set+=tree[rt].lazy;
}
else{
tree[ch].lazy+=tree[rt].lazy;
}
}
if(tree[rt].set){
tree[ch].lazy=0;
tree[ch].set=tree[rt].set;
}
}
void cal_lazy(int l,int r,int rt){ //计算当前节点
if(tree[rt].lazy){
tree[rt].sum+=tree[rt].lazy*(r-l+1);
tree[rt].max+=tree[rt].lazy;
tree[rt].min+=tree[rt].lazy;
}
if(tree[rt].set){
tree[rt].sum = tree[rt].set*(r-l+1);
tree[rt].max=tree[rt].set;
tree[rt].min=tree[rt].set;
}
}
void init_lazy(int rt){//lazy清除
tree[rt].lazy = tree[rt].set = 0;
}
void push_down(int l,int r,int rt){
if(!tree[rt].lazy&&!tree[rt].set) return ;
int ch = rt<<1; int mid=l+r>>1;
cal_lazy(l,r,rt);
if(l!=r){
down_lazy(rt,ch);
down_lazy(rt,ch|1);
}
init_lazy(rt);
}
void push_up(int l,int r,int rt){
int ch = rt<<1; int mid=l+r>>1;
push_down(l,mid,rt<<1);
push_down(mid+1,r,rt<<1|1);
tree[rt].sum = tree[ch].sum + tree[ch|1].sum;
tree[rt].max = max(tree[ch].max , tree[ch|1].max);
tree[rt].min = min(tree[ch].min , tree[ch|1].min);
}
void tree_build(int l,int r,int rt){
tree[rt].lazy=0;
if(l==r){ tree[rt].max=tree[rt].min=tree[rt].sum=a[l]; return; }
int mid =l+r>>1;
tree_build(l,mid,rt<<1);tree_build(mid+1,r,rt<<1|1);
push_up(l,r,rt);
}
void do_sqrt(int a,int b,int l,int r,int rt){
if(a>r||b<l) return ;
push_down(l,r,rt);
int mid = l+r>>1;
if(l>=a&&r<=b){
if(tree[rt].min == tree[rt].max){//区间值相同时候才用懒标记
tree[rt].set =sqrt(tree[rt].max+0.5);//只改懒标记
return ;
}
do_sqrt(a,b,l,mid,rt<<1);
do_sqrt(a,b,mid+1,r,rt<<1|1);
push_up(l,r,rt);
return;
}
do_sqrt(a,b,l,mid,rt<<1);
do_sqrt(a,b,mid+1,r,rt<<1|1);
push_up(l,r,rt);
}
ll get_sum(int a,int b,int l,int r,int rt){
if(a>r||b<l) return 0;
push_down(l,r,rt);
if(l>=a&&r<=b){
return tree[rt].sum;
}
int mid =l+r>>1;
return get_sum(a,b,l,mid,rt<<1)
+ get_sum(a,b,mid+1,r,rt<<1|1);
}
void modify(int a,int b,ll c,int l,int r,int rt ){
if(a>r||b<l) return ;
push_down(l,r,rt);
if(l>=a&&r<=b){ //只改懒标记
tree[rt].lazy+=c;
return ;
}
int mid = l+r>>1;
modify(a,b,c,l,mid,rt<<1);
modify(a,b,c,mid+1,r,rt<<1|1);
push_up(l,r,rt);
}
void de_bug(int l,int r,int rt){//debug-输出原数组的值
if(l==r){ cout<<tree[rt].sum<<" ";return; }
int mid =l+r>>1;
push_down(l,r,rt);
de_bug(l,mid,rt<<1);de_bug(mid+1,r,rt<<1|1);
}
int main(){
int n,m,opt,l,r,x;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
tree_build(1,n,1);//建树
while(m--){
cin>>opt;
if(opt==1){//开方
cin>>l>>r;
do_sqrt(l,r,1,n,1);
}else if(opt==2){//加
cin>>l>>r>>x;
modify(l,r,x,1,n,1);
}else if(opt==3){//查询
cin>>l>>r;
//de_bug(1,n,1);
cout<<get_sum(l,r,1,n,1)<<endl;
}
}
return 0;
}