链接:https://ac.nowcoder.com/acm/contest/26896/1020
来源:牛客网

题目描述

牛牛为了减(吃)肥(好),希望对他的零食序列有更深刻的了解,所以他把他的零食排成一列,然后对每一个零食的美味程度都打了分,现在他有可能执行两种操作:
eat k:吃掉当前的第k个零食。右边的零食全部往左移动一位(编号减一)。

query i j:查询当前第i个零食到第j个零食里面美味度最高的和最低的零食的美味度。

输入描述:

		

第一行包含两个数n, m,表示原始数组的元素个数和操作的个数。第二行包括n个数,表示原始数组。以下m行,每行格式为1 k或者2 i j,其中第一个数为1表示吃掉,为2表示询问。

输出描述:

对每个询问操作输出一行,包括两个数,表示该范围内的最小值和最大值。

题型:

线段树--单点修改与区间查询(最值)

思路:

单点修改与区间查询不需要多说,直接套板子即可。关键在于这个修改之后,右边的值都需要往左移动一位(编号减一)
考虑假删除,即保留该节点,首先需要消除其的最值影响,即将已经被删除的节点的最大值设为-1e9-10,最小值设为1e9+10,这样这个节点就不影响最值的判断。
然后需要消除其的区间占位影响,考虑新开一个cnt数组维护树上每一个tree节点的剩下的未被删除的点的个数,当某个元素被假删除时,将其的cnt值变为0
对于消除其的区间移动影响,考虑新开一个函数专门用于将输入的绝对位置转化成相对位置,即代码二中的posfind函数
如何转化呢?分类讨论
一.当前的查找区间已经变成单点了,即l=r,那么相对位置=l=r;
二.当前的查找区间未变成单点(l<r),此时,我们知道,当前的线段树(tree[p])的左子树(tree[p*2])和右子树(tree[p*2+1])都存储着cnt,即左子树(cnt[p*2]),右子树(tree[p*2+1])分别剩余的元素的个数。
如果输入的绝对位置的值<=cnt[p*2],那么这个绝对位置一定在左子树
如果输入的绝对位置的值>cnt[p*2],那么这个绝对位置一定在右子树
 if(cnt[p*2]>=num) return posfind(p*2,l,mid,num);
 else return posfind(p*2+1,mid+1,r,num-cnt[p*2]); //注意此处num要减去cnt[p*2],因为这里代表着在右子树中查找,而右子树中的值是从1编号的,需要减去左子树的剩余值才等于右子树中的节点的值
总体思路大概就是这样,难度主要在于位置的转化可以新开一个函数专门转化(代码二),也可以把转化的代码并入修改/查询的函数中(代码一),个人认为代码二比代码一要清晰易懂一些,可能是因为代码二更贴近板子吧

代码(在修改与查询的函数中改变位置):

#include<bits/stdc++.h>
#define INF 0x3f3f3f
using namespace std;
const int N=1000000;
int a[N],tree[N*4],tree2[N*4],cnt[N*4];
void build(int p,int l,int r){
	if(l==r){
		cnt[p]=1;
		tree[p]=a[l];
		tree2[p]=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	tree[p]=max(tree[p*2],tree[p*2+1]);
	tree2[p]=min(tree2[p*2],tree2[p*2+1]);
	cnt[p]=cnt[p*2]+cnt[p*2+1];
}

void eat(int p,int l,int r,int num){
	if(l==r){
		cnt[p]=0;
		tree[p]=-1e9-10;
		tree2[p]=1e9+10;
		return;
	}
	int mid=(l+r)/2;
	if(cnt[p*2]>=num) eat(p*2,l,mid,num);
	else eat(p*2+1,mid+1,r,num-cnt[p*2]);
	tree[p]=max(tree[p*2],tree[p*2+1]);
	tree2[p]=min(tree2[p*2],tree2[p*2+1]);
	cnt[p]=cnt[p*2]+cnt[p*2+1];
}

int calc(int p,int l,int r,int x,int y){
	if(x==1 && y==cnt[p]){
		return tree[p];
	}
	int mid=(l+r)/2;
	if(cnt[p*2]>=y) return calc(p*2,l,mid,x,y); 
	if(cnt[p*2]<x) return calc(p*2+1,mid+1,r,x-cnt[p*2],y-cnt[p*2]);
	return max(calc(p*2,l,mid,x,cnt[p*2]),calc(p*2+1,mid+1,r,1,y-cnt[p*2]));
}

int calc2(int p,int l,int r,int x,int y){
	if(x==1 && y==cnt[p]){
		return tree2[p];
	}
	int mid=(l+r)/2;
	if(cnt[p*2]>=y) return calc2(p*2,l,mid,x,y); 
	if(cnt[p*2]<x) return calc2(p*2+1,mid+1,r,x-cnt[p*2],y-cnt[p*2]);
	return min(calc2(p*2,l,mid,x,cnt[p*2]),calc2(p*2+1,mid+1,r,1,y-cnt[p*2]));
}

int main(){
	int n,m;
	//memset(tree2,INF,sizeof(tree2));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	build(1,1,n);
	while(m--){
		int x,y,z;
		scanf("%d",&x);
		if(x==1){
			scanf("%d",&y);
			eat(1,1,n,y);
		}else{
			scanf("%d%d",&y,&z);
			printf("%d %d\n",calc2(1,1,n,y,z),calc(1,1,n,y,z));
		}
	}
	return 0;
} 

代码(新开一个函数专门用于查询位置,修改与查询的函数不变):

#include<bits/stdc++.h>
#define INF 0x3f3f3f
using namespace std;
const int N=1000000;
int a[N],tree[N*4],tree2[N*4],cnt[N*4];
void build(int p,int l,int r){
	if(l==r){
		cnt[p]=1;
		tree[p]=a[l];
		tree2[p]=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	tree[p]=max(tree[p*2],tree[p*2+1]);
	tree2[p]=min(tree2[p*2],tree2[p*2+1]);
	cnt[p]=cnt[p*2]+cnt[p*2+1];
}

void eat(int p,int l,int r,int num){
	if(l==r){
		cnt[p]=0;
		tree[p]=-1e9-10;
		tree2[p]=1e9+10;
		return;
	}
	int mid=(l+r)/2;
	if(num<=mid) eat(p*2,l,mid,num);
	else eat(p*2+1,mid+1,r,num);
	tree[p]=max(tree[p*2],tree[p*2+1]);
	tree2[p]=min(tree2[p*2],tree2[p*2+1]);
	cnt[p]=cnt[p*2]+cnt[p*2+1];
}

int calc(int p,int l,int r,int x,int y){
	if(x<=l && y>=r){
		return tree[p];
	}
	int mid=(l+r)/2;
	if(y<=mid) return calc(p*2,l,mid,x,y); 
	if(x>=mid+1) return calc(p*2+1,mid+1,r,x,y);
	return max(calc(p*2,l,mid,x,mid),calc(p*2+1,mid+1,r,mid+1,y));
}

int calc2(int p,int l,int r,int x,int y){
	if(x<=l && y>=r){
		return tree2[p];
	}
	int mid=(l+r)/2;
	if(y<=mid) return calc2(p*2,l,mid,x,y); 
	if(x>=mid+1) return calc2(p*2+1,mid+1,r,x,y);
	return min(calc2(p*2,l,mid,x,mid),calc2(p*2+1,mid+1,r,mid+1,y));
}

int posfind(int p,int l,int r,int num){ //专门用于查询位置的函数
	if(l==r) return l;
	int mid=(l+r)/2;
	if(cnt[p*2]>=num) return posfind(p*2,l,mid,num);
	else return posfind(p*2+1,mid+1,r,num-cnt[p*2]);
}

int main(){
	int n,m;
	//memset(tree2,INF,sizeof(tree2));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	build(1,1,n);
	while(m--){
		int x,y,z;
		scanf("%d",&x);
		if(x==1){
			scanf("%d",&y);
			y=posfind(1,1,n,y);
			eat(1,1,n,y);
		}else{
			scanf("%d%d",&y,&z);
			y=posfind(1,1,n,y);
			z=posfind(1,1,n,z);
			printf("%d %d\n",calc2(1,1,n,y,z),calc(1,1,n,y,z));
		}
	}
	return 0;
}