势能线段树
势能:
信息学中,势能被用于计算某一个过程,或者某一类过程时间复杂度的总和
例如计算两个数之间的最大公倍数gcd的时间复杂度是O(log N),而计算n个数之间的时间复杂度是O(n+log N)
总复杂度=数组循环遍历复杂度+gcd函数被调用的总复杂度
势能均摊复杂度
势能均摊复杂度是指把总的时间复杂度摊到操作次数或者循环次数上面
在上述n个数之间的gcd的时间复杂度为:
O
(
N
+
l
o
g
C
)
N
=
O
(
1
+
(
log
C
N
)
)
=
O
(
1
)
\frac{O(N+logC)}{N}=O(1+(\frac{\log C}{N}))=O(1)
NO(N+logC)=O(1+(NlogC))=O(1)
则线段树维护区间gcd时间复杂度为O(nlogn)
有了上述势能知识可以更好地帮助我们计算时间复杂度
模板
链接:https://ac.nowcoder.com/acm/contest/19917/D
来源:牛客网
则势能线段树则总时间复杂度为{O(M \times |0势能时线段树操作时间复杂度|+N\times |节点势能上限降低至0势能时间复杂度|)}O(M×∣0势能时线段树操作时间复杂度∣+N×∣节点势能上限降低至0势能时间复杂度∣)。
使用势能线段树时要定义势能、势能初始值(势能最大值)、0势能点。
本题中有两种可以定义势能与0势能的方法,你可以都尝试一下。
定义区间开根次数cnt为势能,势能初始值为势能上限=6,定义0势能点为cnt=0。
定义区间最大值max为势能,势能初始值为区间最大值,定义0势能点为max=1。
给你一个长度大小为{N}N的正整数数组,进行{M}M次操作,操作有下列两种。
1.
给
定
区
间
[
l
,
r
]
对
区
间
中
所
有
数
字
开
根
号
向
下
取
整
,
即
a
i
=
⌊
a
i
⌋
(
l
≤
i
≤
r
)
(
l
≤
i
≤
r
)
。
1.给定区间[l,r]对区间中所有数字开根号向下取整,即a_i= \lfloor \sqrt{a_i} \rfloor(l \leq i \leq r)(l≤i≤r)。
1.给定区间[l,r]对区间中所有数字开根号向下取整,即ai=⌊ai⌋(l≤i≤r)(l≤i≤r)。
查
询
给
定
区
间
[
l
,
r
]
的
元
素
和
,
即
求
∑
i
=
l
r
a
i
查询给定区间[l,r]的元素和,即求\sum_{i=l}^{r}a_{i}
查询给定区间[l,r]的元素和,即求i=l∑rai
简单来说,通过记录区间min和max值,在进行开方的时候max和min是同步操作的。每一次开方都在加速是的max-min的值越来越小,max-min=0也就是势能零点。因此这里考虑类似于dfs中的剪枝,当max-min=0的情况下可以对区间进行统一操作:
#include<bits/stdc++.h>
using namespace std;
#define N 200010
#define int long long
typedef long long LL;
#define int long long
int n,m;
int w[N];
struct Node{
int l,r;
LL sum,max,min;
LL lazy,set; //set是区间置数
}tr[N<<4];
void push_up(int u){
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
tr[u].min=min(tr[u<<1].min,tr[u<<1|1].min);
tr[u].max=max(tr[u<<1].max,tr[u<<1|1].max);
tr[u].lazy=tr[u].set=0;
}
void val(Node &u,int lazy,int set){
if(lazy){
u.sum+=lazy*(u.r-u.l+1);
u.max+=lazy;
u.min+=lazy;
if(u.set)
u.set+=lazy;
else
u.lazy+=lazy;
}
if(set){
u.sum=set*(u.r-u.l+1);
u.max=set;
u.min=set;
u.lazy=0;
u.set=set;
}
}
void push_down(int u){
val(tr[u<<1], tr[u].lazy, tr[u].set);
val(tr[u<<1|1], tr[u].lazy, tr[u].set);
tr[u].lazy=0,tr[u].set=0;
}
void build(int u,int l,int r){
tr[u]={l,r};
if(l==r){
tr[u].sum=tr[u].max=tr[u].min=w[l];
tr[u].lazy=tr[u].set=0;
return ;
}
int mid= l+r >> 1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
push_up(u);
}
void tsqrt(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r){
if(tr[u].min==tr[u].max){
val(tr[u],0,sqrt(tr[u].max));
return ;
}
push_down(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) tsqrt(u<<1,l,r);
if(r>mid) tsqrt(u<<1|1,l,r);
push_up(u);
return ;
}
push_down(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) tsqrt(u<<1,l,r);
if(r>mid) tsqrt(u<<1|1,l,r);
push_up(u);
}
int query(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum;
push_down(u);
int mid=tr[u].l+tr[u].r>>1;
int ans=0;
if(l<=mid) ans+=query(u<<1,l,r);
if(r>mid) ans+=query(u<<1|1,l,r);
return ans;
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&w[i]);
build(1,1,n);
for(int i=1;i<=m;i++){
int op,l,r,x;
scanf("%lld%lld%lld",&op,&l,&r);
if(op==1){
tsqrt(1, l, r);
}
else if(op==2){
printf("%lld\n",query( 1, l, r));
}
}
return 0;
}
模板二:比前1问多一个区间加和
给
定
区
间
[
l
,
r
]
,
对
区
间
中
每
个
数
字
加
上
一
个
正
整
数
x
。
给定区间{[l,r]},对区间中每个数字加上一个正整数{x}。
给定区间[l,r],对区间中每个数字加上一个正整数x。
没什么区别
#include<bits/stdc++.h>
using namespace std;
#define N 200010
#define int long long
typedef long long LL;
#define int long long
int n,m;
int w[N];
struct Node{
int l,r;
LL sum,max,min;
LL lazy,set; //set是区间置数
}tr[N<<4];
void push_up(int u){
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
tr[u].min=min(tr[u<<1].min,tr[u<<1|1].min);
tr[u].max=max(tr[u<<1].max,tr[u<<1|1].max);
tr[u].lazy=tr[u].set=0;
}
void val(Node &u,int lazy,int set){
if(lazy){
u.sum+=lazy*(u.r-u.l+1);
u.max+=lazy;
u.min+=lazy;
if(u.set)
u.set+=lazy;
else
u.lazy+=lazy;
}
if(set){
u.sum=set*(u.r-u.l+1);
u.max=set;
u.min=set;
u.lazy=0;
u.set=set;
}
}
void push_down(int u){
val(tr[u<<1], tr[u].lazy, tr[u].set);
val(tr[u<<1|1], tr[u].lazy, tr[u].set);
tr[u].lazy=0,tr[u].set=0;
}
void build(int u,int l,int r){
tr[u]={l,r};
if(l==r){
tr[u].sum=tr[u].max=tr[u].min=w[l];
tr[u].lazy=tr[u].set=0;
return ;
}
int mid= l+r >> 1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
push_up(u);
}
void tadd(int u,int l,int r,int x){
if(tr[u].l>=l&&tr[u].r<=r){
val(tr[u],x,0);
return ;
}
push_down(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) tadd(u<<1,l,r,x);
if(r>mid) tadd(u<<1|1,l,r,x);
push_up(u);
}
void tsqrt(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r){
if(tr[u].min==tr[u].max){
val(tr[u],0,sqrt(tr[u].max));
return ;
}
push_down(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) tsqrt(u<<1,l,r);
if(r>mid) tsqrt(u<<1|1,l,r);
push_up(u);
return ;
}
push_down(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) tsqrt(u<<1,l,r);
if(r>mid) tsqrt(u<<1|1,l,r);
push_up(u);
}
int query(int u,int l,int r){
if(tr[u].l>=l&&tr[u].r<=r) return tr[u].sum;
push_down(u);
int mid=tr[u].l+tr[u].r>>1;
int ans=0;
if(l<=mid) ans+=query(u<<1,l,r);
if(r>mid) ans+=query(u<<1|1,l,r);
return ans;
}
signed main(){
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&w[i]);
build(1,1,n);
for(int i=1;i<=m;i++){
int op,l,r,x;
scanf("%lld%lld%lld",&op,&l,&r);
if(op==1){
tsqrt(1, l, r);
}
else if(op==3){
printf("%lld\n",query( 1, l, r));
}
else if(op==2){
scanf("%lld",&x);
tadd( 1, l, r, x);
}
}
return 0;
}
Ques:弩蚊怒夏(详细)
链接:https://ac.nowcoder.com/acm/contest/19917/B
来源:牛客网
题目描述
长长的夏日终于黑下来了。
现在有许多大小各异的蚊子,它们聚集在一维区间内的某一个点上。
你一巴掌拍在一段区间上,拍死了一些蚊子,而一些体形较小的蚊子能从你的指缝逃离。
具
体
的
来
说
,
共
有
m
只
蚊
子
,
每
一
只
蚊
子
在
[
1
,
n
]
内
的
一
点
,
并
且
第
i
只
蚊
子
具
有
它
的
体
形
a
i
具体的来说,共有{m}只蚊子,每一只蚊子在{[1,n]}内的一点,并且第i只蚊子具有它的体形a_{i}
具体的来说,共有m只蚊子,每一只蚊子在[1,n]内的一点,并且第i只蚊子具有它的体形ai
你
会
拍
k
次
蚊
子
,
第
i
次
在
区
间
[
L
i
,
R
i
]
[
L
i
,
R
i
]
内
拍
死
体
形
大
于
等
于
p
i
你会拍{k}次蚊子,第i次在区间\begin{bmatrix} L_{i},R_{i} \end{bmatrix}[ L i,R_i]内拍死体形大于等于p_{i}
你会拍k次蚊子,第i次在区间[Li,Ri][Li,Ri]内拍死体形大于等于pi
的蚊子,请按顺序输出每次拍死蚊子体形大小的总和。
输入描述:
第
一
行
输
入
n
,
m
,
k
并
且
n
∈
[
1
,
1
0
5
]
,
m
∈
[
1
,
1
0
5
]
,
k
∈
[
1
,
1
0
5
]
分
别
表
示
总
区
间
为
[
1
,
n
]
,
有
m
只
蚊
子
,
k
次
拍
蚊
子
。
第一行输入n,m,k并且n \in \begin{bmatrix} 1,10^{5} \end{bmatrix},m \in \begin{bmatrix} 1,10^{5} \end{bmatrix},k \in \begin{bmatrix} 1,10^{5} \end{bmatrix}分别表示总区间为\begin{bmatrix} 1,n \end{bmatrix},有m只蚊子,k次拍蚊子。
第一行输入n,m,k并且n∈[1,105],m∈[1,105],k∈[1,105]分别表示总区间为[1,n],有m只蚊子,k次拍蚊子。
接
下
来
输
入
m
行
,
每
行
有
p
o
s
i
,
并
且
p
o
s
i
∈
[
1
,
1
0
5
]
,
a
i
∈
[
1
,
1
0
9
]
接下来输入m行,每行有pos_{i} ,并且pos_{i} \in \begin{bmatrix} 1,10^{5} \end{bmatrix},a_{i}\in \begin{bmatrix} 1,10^{9} \end{bmatrix}
接下来输入m行,每行有posi,并且posi∈[1,105],ai∈[1,109]
]分别表示这只蚊子的位置和体形。
接
下
来
输
入
k
行
,
每
行
有
L
i
,
R
i
,
P
i
接下来输入k行,每行有L_{i},R_{i},P_{i}
接下来输入k行,每行有Li,Ri,Pi
并
且
L
i
∈
[
1
,
n
]
,
R
i
∈
[
L
i
,
n
]
,
P
i
∈
[
1
,
1
0
9
]
并且L_{i} \in \begin{bmatrix} 1,n \end{bmatrix},R_{i} \in \begin{bmatrix} L_{i},n \end{bmatrix},P_{i} \in \begin{bmatrix} 1,10^{9} \end{bmatrix}
并且Li∈[1,n],Ri∈[Li,n],Pi∈[1,109]
输出描述:
输
出
k
行
,
每
行
输
出
一
个
s
u
m
i
输出k行,每行输出一个sum_{i}
输出k行,每行输出一个sumi
表示此次拍死蚊子体形大小的总和。
示例1
输入
复制
5 5 4
1 5
1 2
3 4
3 3
4 9
1 3 3
1 3 2
1 3 2
1 5 6
输出
复制
12
2
0
9
这道题RT,会出现同一个点上有不同大小的蚊子,有的会被打死有的会逃脱,因此我们可以建一个大根堆来存储
在建立好线段树之后,我们需要考虑如何处理每一次询问,即怎么样高效地返回大于等于巴掌大小的蚊子数量,考虑朴素方案:遍历所有叶子节点,从大根堆中依次取出符合条件的蚊子打死,累计答案返回。这样子大概率会T
因此和dfs一样,我们呢考虑进行剪枝:
1.当这个区间内的最小值大于巴掌大小的时候所有蚊子都会被打死(这听这也太爽了),因此直接遍历到这个区间的时候放回总和sum即可
2.当区间内的最大值小于巴掌大小的时候,一只蚊子也打不死(返回0)
3.当不满足上述两种情况,只能遍历到叶子节点来进行修改了
然后这道题就已经算写完了,不过别忘了考虑一点,这里是否需要用到懒标记?他表示什么?
显然懒标记的用处在于表示这个区间是否被完全消灭,因此push_down操作也只需要在存在懒标记的时候进行即可
#include<bits/stdc++.h>
using namespace std;
#define N 100010
#define int long long
int n,m,k,cur=1;
int sum[N<<4];
int mn[N<<4],mx[N<<4];
priority_queue<int> Q[N<<4];
bool tag[N<<4];
void push_up(int u){ //子节点更新父节点
sum[u]=sum[u<<1]+sum[u<<1|1];
mn[u]=min(mn[u<<1],mn[u<<1|1]),mx[u]=max(mx[u<<1],mx[u<<1|1]);
}
void push_down(int u,int l,int r){ //下传懒标记,懒标记意味着区间蚊子全被拍死,用于剪枝优化
if(tag[u]){
tag[u<<1]=tag[u<<1|1]=true;
sum[u<<1]=sum[u<<1|1]=0;
mx[u<<1]=mx[u<<1|1]=0;
int mid=l+r>>1;
if(l<=mid){
while(Q[u<<1].size())
Q[u<<1].pop();
}
if(r>mid){
while(Q[u<<1|1].size())
Q[u<<1|1].pop();
}
tag[u]=false;
}
}
void insert(int u,int l,int r,int pos,int x){ //单点修改
if(l==r){
sum[u]+=x;
mx[u]=max(mx[u],x);
mn[u]=Q[u].empty()?x:min(mn[u],x);
Q[u].push(x);
return ;
}
push_down(u,l,r);
int mid=l+r>>1;
if(pos<=mid) insert(u<<1,l,mid,pos,x);
else insert(u<<1|1,mid+1,r,pos,x);
push_up(u);
}
int query(int u,int ul,int ur,int l,int r,int sz){
int ans=0;
if(ul>=l&&ur<=r){ //查询区间可被完全覆盖
if(mx[u]<sz) return 0; //一只蚊子也拍不死
if(ul==ur){ //子节点
while(Q[u].size()){
mx[u]=Q[u].top(); //更新max
int xx=Q[u].top();
if(xx<sz) break;
Q[u].pop();
ans+=xx;
sum[u]-=xx;
}
if(Q[u].empty()) mx[u]=mn[u]=0; //全部被拍死,更新min和max
return ans;
}
if(mn[u]>=sz){ //如果区间最小值大于sz直接全部返回
ans=sum[u];
sum[u]=0;
tag[u]=true;
return ans;
}
push_down(u, ul, ur);
int mid=ul+ur>>1;
if(l<=mid) ans+=query(u<<1,ul,mid,l,r,sz);
if(r>mid) ans+=query(u<<1|1,mid+1,ur,l,r,sz);
push_up(u);
return ans;
}
push_down(u,ul,ur);
int mid=ul+ur>>1;
if(l<=mid) ans+=query(u<<1,ul,mid,l,r,sz);
if(r>mid) ans+=query(u<<1|1,mid+1,ur,l,r,sz);
push_up(u);
return ans;
}
signed main(){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1;i<=m;i++){
int pos,a;
scanf("%lld%lld",&pos,&a);
insert(1,1,n,pos,a);
}
while(k--){
int l,r,sz;
scanf("%lld%lld%lld",&l,&r,&sz);
printf("%lld\n",query(1,1,n,l,r,sz));
}
return 0;
}