LOJ6029 市场 和 UOJ228 基础数据结构练习题

区间除法、区间开根这类套路题。虽然实现很简单,但是复杂度证明比较复杂。

LOJ6029 市场

从前有一个贸易市场,在一位执政官到来之前都是非常繁荣的,自从他来了之后,发布了一系列奇怪的政令,导致贸易市场的衰落。

\(n\) 个商贩,从 \(0 \sim n - 1\) 编号,每个商贩的商品有一个价格 \(a_i\),有两种政令;同时,有一个外乡的旅客想要了解贸易市场的信息,有两种询问方式:

  1. (政令)\(l, r, c\),对于 \(i \in [l, r], a_i \leftarrow a_i + c\)

  2. (政令)\(l, r, d\),对于 \(i \in [l, r], a_i \leftarrow \lfloor {a_i}/{d} \rfloor\)

  3. (询问)给定 \(l, r\),求 \(\min_{i \in [l, r]} a_i\)

  4. (询问)给定 \(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;
}

posted on 2020-04-23 15:47  autoint  阅读(178)  评论(0编辑  收藏  举报

导航