链接: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编号的,需要减去左子树的剩余值才等于右子树中的节点的值
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; }