LOJ6029 市场 和 UOJ228 基础数据结构练习题
区间除法、区间开根这类套路题。虽然实现很简单,但是复杂度证明比较复杂。
LOJ6029 市场
从前有一个贸易市场,在一位执政官到来之前都是非常繁荣的,自从他来了之后,发布了一系列奇怪的政令,导致贸易市场的衰落。
有 \(n\) 个商贩,从 \(0 \sim n - 1\) 编号,每个商贩的商品有一个价格 \(a_i\),有两种政令;同时,有一个外乡的旅客想要了解贸易市场的信息,有两种询问方式:
-
(政令)\(l, r, c\),对于 \(i \in [l, r], a_i \leftarrow a_i + c\)
-
(政令)\(l, r, d\),对于 \(i \in [l, r], a_i \leftarrow \lfloor {a_i}/{d} \rfloor\)
-
(询问)给定 \(l, r\),求 \(\min_{i \in [l, r]} a_i\)
-
(询问)给定 \(l, r\),求 \(\sum_{i\in [l, r]} a_i\)
对于 \(100\%\) 的数据,\(1 \leq n, q \leq 10 ^ 5, 0 \leq l \leq r \leq n - 1, c \in [-10 ^ {4}, 10 ^ 4], d \in [2, 10 ^ 9]\)。
题解
https://www.cnblogs.com/GXZlegend/p/8715279.html
对于原来的两个数 a 和 b ( a>b ) ,原来的差是 a-b ,都除以 d 后的差是 \(\frac{a-b}d\) ,相当于差也除了 d 。
而当区间差为 0 或 a=kd,b=kd-1 的 1 时,区间下取整除就变成了区间减。
因此当一个区间下取整除了 \(\log(Max-Min)\) 次后就不需要暴力下取整除,直接区间减即可。
定义线段树节点势能为 \(\log(Max-Min)\) ,那么每次对 [l,r] 下取整除就是将所有 \(l\le x,y\le r\) ,且势能不为 0 的节点 [x,y] 的势能减 1 ,代价为势能减少总量。
分析区间加操作:只会修改到经过的节点的势能,影响 \(\log\) 个节点,将这些点的势能恢复为 \(\log(Max-Min)\) 。
因此总的时间复杂度就是总势能量 \(O((n+m\log n)\log a)\) 。
注意C++中的 '/' 并不是下取整,而是向0取整,因此需要按正负分类讨论手写下取整。或者直接用浮点数的floor
。
struct node {int min,max;int64 sum;};
IN node operator*(CO node&a,CO node&b){ // merge
return {min(a.min,b.min),max(a.max,b.max),a.sum+b.sum};
}
IN node operator+(CO node&a,CO node&b){
return {a.min+b.min,a.max+b.max,a.sum+b.sum};
}
CO int N=1e5+10;
node tree[4*N];
int tag[4*N];
#define lc (x<<1)
#define rc (x<<1|1)
#define mid ((l+r)>>1)
void build(int x,int l,int r){
if(l==r){
int v=read<int>();
tree[x]={v,v,v};
return;
}
build(lc,l,mid),build(rc,mid+1,r);
tree[x]=tree[lc]*tree[rc];
}
void push_down(int x,int l,int r){
if(tag[x]){
tree[lc]=tree[lc]+(node){tag[x],tag[x],(int64)tag[x]*(mid-l+1)},tag[lc]+=tag[x];
tree[rc]=tree[rc]+(node){tag[x],tag[x],(int64)tag[x]*(r-mid)},tag[rc]+=tag[x];
tag[x]=0;
}
}
void ins_add(int x,int l,int r,int ql,int qr,int v){
if(ql<=l and r<=qr){
tree[x]=tree[x]+(node){v,v,(int64)v*(r-l+1)},tag[x]+=v;
return;
}
push_down(x,l,r);
if(ql<=mid) ins_add(lc,l,mid,ql,qr,v);
if(qr>mid) ins_add(rc,mid+1,r,ql,qr,v);
tree[x]=tree[lc]*tree[rc];
}
void ins_div(int x,int l,int r,int ql,int qr,int v){
if(ql<=l and r<=qr){
if(-1<=tree[x].min and tree[x].max<=0) return;
if(l==r){
int w=floor((float128)tree[x].min/v);
tree[x]={w,w,w};
return;
}
push_down(x,l,r);
if(floor((float128)tree[x].min/v)-tree[x].min==floor((float128)tree[x].max/v)-tree[x].max){
int delta=floor((float128)tree[x].min/v)-tree[x].min;
tree[x]=tree[x]+(node){delta,delta,(int64)delta*(r-l+1)},tag[x]+=delta;
return;
}
ins_div(lc,l,mid,ql,qr,v);
ins_div(rc,mid+1,r,ql,qr,v);
tree[x]=tree[lc]*tree[rc];
return;
}
push_down(x,l,r);
if(ql<=mid) ins_div(lc,l,mid,ql,qr,v);
if(qr>mid) ins_div(rc,mid+1,r,ql,qr,v);
tree[x]=tree[lc]*tree[rc];
}
node query(int x,int l,int r,int ql,int qr){
if(ql<=l and r<=qr) return tree[x];
push_down(x,l,r);
if(qr<=mid) return query(lc,l,mid,ql,qr);
if(ql>mid) return query(rc,mid+1,r,ql,qr);
return query(lc,l,mid,ql,qr)*query(rc,mid+1,r,ql,qr);
}
#undef lc
#undef rc
#undef mid
int main(){
int n=read<int>(),m=read<int>();
build(1,1,n);
while(m--){
int opt=read<int>();
if(opt==1){
int l=read<int>()+1,r=read<int>()+1,c=read<int>();
ins_add(1,1,n,l,r,c);
}
else if(opt==2){
int l=read<int>()+1,r=read<int>()+1,d=read<int>();
ins_div(1,1,n,l,r,d);
}
else if(opt==3){
int l=read<int>()+1,r=read<int>()+1;
node x=query(1,1,n,l,r);
printf("%d\n",x.min);
}
else{
int l=read<int>()+1,r=read<int>()+1;
node x=query(1,1,n,l,r);
printf("%lld\n",x.sum);
}
}
return 0;
}
UOJ228 基础数据结构练习题
sylvia 是一个热爱学习的女孩子,今天她想要学习数据结构技巧。
在看了一些博客学了一些姿势后,她想要找一些数据结构题来练练手。于是她的好朋友九条可怜酱给她出了一道题。
给出一个长度为 \(n\) 的数列 \(A\),接下来有 \(m\) 次操作,操作有三种:
对于所有的 \(i \in [l,r]\),将 \(A_i\) 变成 \(A_i+x\)。
对于所有的 \(i \in [l,r]\),将 \(A_i\) 变成 \(\lfloor \sqrt {A_i} \rfloor\)。
对于所有的 \(i \in [l,r]\),询问 \(A_i\) 的和。
作为一个不怎么熟练的初学者,sylvia 想了好久都没做出来。而可怜酱又外出旅游去了,一时间联系不上。于是她决定向你寻求帮助:你能帮她解决这个问题吗。
对于所有数据,保证有 \(n \leq 100000\),\(m \leq 100000\),\(1 \leq l_i \leq r_i \leq n,1 \leq A_i,x_i \leq 10^5\)。
题解
https://www.cnblogs.com/GXZlegend/p/8709518.html
对于原来的两个数 a 和 b ( a>b ) ,开根后变成 \(\sqrt a\) 和 \(\sqrt b\) ,它们的差从 a-b 变成了 \(\sqrt a-\sqrt b\) 。
又有 \((\sqrt a-\sqrt b)(\sqrt a+\sqrt b)=a-b\) ,因此开方后的差小于原来差的开方。
而当区间差为 0 或 \(a=x^2,b=x^2-1\) 的 1 时,区间开根就变成了区间减。
因此一个区间开根 \(\log\log(Max-Min)\) 次后就不需要暴力开根,直接区间减即可。
定义线段树节点势能为 \(\log\log(Max-Min)\) ,那么每次对 [l,r] 开根就是将所有 \(l\le x,y\le r\) ,且势能不为 0 的节点 [x,y] 的势能减 1 ,代价为势能减少总量。
分析区间加操作:只会修改到经过的节点的势能,影响 \(\log\) 个节点,将这些点的势能恢复为 \(\log\log(Max-Min)\)。
因此总的时间复杂度就是总势能量 \(O((n+m\log n)\log\log a)\)。
struct node {int64 min,max,sum;};
IN node operator*(CO node&a,CO node&b){ // merge
return {min(a.min,b.min),max(a.max,b.max),a.sum+b.sum};
}
IN node operator+(CO node&a,CO node&b){
return {a.min+b.min,a.max+b.max,a.sum+b.sum};
}
CO int N=1e5+10;
node tree[4*N];
int64 tag[4*N];
#define lc (x<<1)
#define rc (x<<1|1)
#define mid ((l+r)>>1)
void build(int x,int l,int r){
if(l==r){
int64 v=read<int64>();
tree[x]={v,v,v};
return;
}
build(lc,l,mid),build(rc,mid+1,r);
tree[x]=tree[lc]*tree[rc];
}
void push_down(int x,int l,int r){
if(tag[x]){
tree[lc]=tree[lc]+(node){tag[x],tag[x],tag[x]*(mid-l+1)},tag[lc]+=tag[x];
tree[rc]=tree[rc]+(node){tag[x],tag[x],tag[x]*(r-mid)},tag[rc]+=tag[x];
tag[x]=0;
}
}
void ins_add(int x,int l,int r,int ql,int qr,int64 v){
if(ql<=l and r<=qr){
tree[x]=tree[x]+(node){v,v,v*(r-l+1)},tag[x]+=v;
return;
}
push_down(x,l,r);
if(ql<=mid) ins_add(lc,l,mid,ql,qr,v);
if(qr>mid) ins_add(rc,mid+1,r,ql,qr,v);
tree[x]=tree[lc]*tree[rc];
}
void ins_sqrt(int x,int l,int r,int ql,int qr){
if(ql<=l and r<=qr){
if(tree[x].max==1) return;
if(l==r){
int64 w=floor(sqrt(tree[x].min));
tree[x]={w,w,w};
return;
}
push_down(x,l,r);
if(floor(sqrt(tree[x].min))-tree[x].min==floor(sqrt(tree[x].max))-tree[x].max){
int64 delta=floor(sqrt(tree[x].min))-tree[x].min;
tree[x]=tree[x]+(node){delta,delta,delta*(r-l+1)},tag[x]+=delta;
return;
}
ins_sqrt(lc,l,mid,ql,qr);
ins_sqrt(rc,mid+1,r,ql,qr);
tree[x]=tree[lc]*tree[rc];
return;
}
push_down(x,l,r);
if(ql<=mid) ins_sqrt(lc,l,mid,ql,qr);
if(qr>mid) ins_sqrt(rc,mid+1,r,ql,qr);
tree[x]=tree[lc]*tree[rc];
}
node query(int x,int l,int r,int ql,int qr){
if(ql<=l and r<=qr) return tree[x];
push_down(x,l,r);
if(qr<=mid) return query(lc,l,mid,ql,qr);
if(ql>mid) return query(rc,mid+1,r,ql,qr);
return query(lc,l,mid,ql,qr)*query(rc,mid+1,r,ql,qr);
}
#undef lc
#undef rc
#undef mid
int main(){
int n=read<int>(),m=read<int>();
build(1,1,n);
while(m--){
int opt=read<int>();
if(opt==1){
int l=read<int>(),r=read<int>();
ins_add(1,1,n,l,r,read<int64>());
}
else if(opt==2){
int l=read<int>(),r=read<int>();
ins_sqrt(1,1,n,l,r);
}
else{
int l=read<int>(),r=read<int>();
node x=query(1,1,n,l,r);
printf("%lld\n",x.sum);
}
}
return 0;
}