题目
解法
由于有区间加,直接暴力做除法的复杂度是不对的。
不过我们可以尝试将其转化为区间加法:若对于某个区间的 \(p=\max,q=\min\),有
\[\Delta =p-\left \lfloor\frac{p}{d} \right \rfloor=q-\left \lfloor\frac{q}{d} \right \rfloor
\]
那么整个区间相当于减去 \(\Delta\)。
具体可以从这个角度理解:\(y=x\) 的增长速度比 \(y=\left \lfloor\frac{x}{d} \right \rfloor,d\ge 1\) 更快,所以当 \(x\) 变大时,\(\Delta\) 也在变大。
但如果不满足这个条件,复杂度看似仍然会炸啊?
我们考虑用势能分析解决这个问题。定义一个代表 \([l,r]\) 区间的节点的势能为 \(\log(\max_{l,r}-\min_{l,r})\)。整棵树的势能就是所有节点的势能之和。
查询操作并不引起势能变化,而且复杂度是显然的,不在我们的考虑范围之内。
对于区间修改,暂时先考虑不完全被 \([l,r]\) 覆盖的区间的节点的势能变化:单个节点最坏情况增加 \(\log V\) 的势能(\(V\) 是值域),这样的区间等价于分别包含 \(l,r\) 端点的区间,所以是 \(\log n\) 级别的。那么总共只会增加 \(q\cdot \log n\log V\) 的势能。
对于完全被 \([l,r]\) 覆盖的区间的节点,考虑一棵二叉树的节点个数大概是二倍值域大小,所以完全被 \([l,r]\) 覆盖的区间的节点个数在 \(r-l+1\) 级别。需要分两种操作讨论:
- 区间加。显然势能是不变的。总时间复杂度 \(\mathcal O(q\log n)\)。
- 区间除。
- \(\max_{L,R}-\min_{L,R}\le 1\)。设满足此条件的节点个数为 \(t\)。此时势能极小而且基本不变了,而且它大概率满足上文优化的条件。所以我们认为它是 \(\mathcal O(\log n)\) 级别的。
- \(\max_{L,R}-\min_{L,R}> 1\)。由于 \(d>1\),那么 \(\max_{L,R}-\min_{L,R}\) 至少减半,也即势能至少减 \(1\)。修改这部分节点复杂度是 \(\mathcal O(r-l+1-t)\) 级别的,但同时势能至少减少 \(r-l+1-t\)。当减少到 \(\max_{L,R}-\min_{L,R}\le 1\) 时,就又变成了上面的问题。
所以总复杂度应该也就是积蓄的最大势能:\(\mathcal O(q\cdot \log n\log V)\)。
代码
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f|=(s=='-');
while(s>='0' and s<='9')
x=(x<<1)+(x<<3)+(s^48),
s=getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-'),write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <cmath>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int a[maxn];
struct node {
ll s;
int tag,mx,mn;
} t[maxn<<2];
void pushUp(int o) {
t[o].s=t[o<<1].s+t[o<<1|1].s;
t[o].mx=max(t[o<<1].mx,t[o<<1|1].mx);
t[o].mn=min(t[o<<1].mn,t[o<<1|1].mn);
}
void build(int o,int l,int r) {
if(l==r) {
t[o].s=t[o].mx=t[o].mn=a[l];
return;
}
int mid=l+r>>1;
build(o<<1,l,mid);
build(o<<1|1,mid+1,r);
pushUp(o);
}
void pushDown(int o,int L,int R) {
if(!t[o].tag)
return;
int l=o<<1,r=o<<1|1,mid=L+R>>1;
t[l].s+=1ll*t[o].tag*(mid-L+1),t[r].s+=1ll*t[o].tag*(R-mid);
t[l].mx+=t[o].tag,t[r].mx+=t[o].tag;
t[l].mn+=t[o].tag,t[r].mn+=t[o].tag;
t[l].tag+=t[o].tag,t[r].tag+=t[o].tag;
t[o].tag=0;
}
void div(int o,int l,int r,int L,int R,int d) {
if(l>R or r<L) return;
if(l>=L and r<=R) {
int p=t[o].mx-(int)floor(1.0*t[o].mx/d);
int q=t[o].mn-(int)floor(1.0*t[o].mn/d);
if(p==q) {
t[o].s-=1ll*p*(r-l+1);
t[o].mx-=p,t[o].mn-=p;
t[o].tag-=p;
return;
}
}
int mid=l+r>>1;
pushDown(o,l,r);
div(o<<1,l,mid,L,R,d);
div(o<<1|1,mid+1,r,L,R,d);
pushUp(o);
}
void modify(int o,int l,int r,int L,int R,int d) {
if(l>R or r<L) return;
if(l>=L and r<=R) {
t[o].s+=1ll*(r-l+1)*d;
t[o].mn+=d,t[o].mx+=d;
t[o].tag+=d;
return;
}
int mid=l+r>>1;
pushDown(o,l,r);
modify(o<<1,l,mid,L,R,d);
modify(o<<1|1,mid+1,r,L,R,d);
pushUp(o);
}
ll ask(int o,int l,int r,int L,int R) {
if(l>R or r<L) return 0;
if(l>=L and r<=R) return t[o].s;
int mid=l+r>>1;
pushDown(o,l,r);
return ask(o<<1,l,mid,L,R)+
ask(o<<1|1,mid+1,r,L,R);
}
int m_ask(int o,int l,int r,int L,int R) {
if(l>R or r<L) return 2e9;
if(l>=L and r<=R) return t[o].mn;
int mid=l+r>>1;
pushDown(o,l,r);
return min(m_ask(o<<1,l,mid,L,R),
m_ask(o<<1|1,mid+1,r,L,R));
}
int main() {
int n=read(9),q=read(9);
for(int i=1;i<=n;++i)
a[i]=read(9);
build(1,1,n);
int op,l,r,x;
while(q--) {
op=read(9);
l=read(9)+1,r=read(9)+1;
if(op==1)
modify(1,1,n,l,r,read(9));
else if(op==2)
div(1,1,n,l,r,read(9));
else if(op==3)
print(m_ask(1,1,n,l,r),'\n');
else print(ask(1,1,n,l,r),'\n');
}
return 0;
}