minmax动态最值

动态最值

minmax

有一个包含n个元素的数组,要求实现以下操作:

DELETE k:删除位置k上的数。右边的数往左移一个位置。

QUERY i j:查询位置i~j上所有数的最小值和最大值。

例如有10个元素:

位置

1

2

3

4

5

6

7

8

9

10

元素

1

5

2

6

7

4

9

3

1

5

QUERY 2 8的结果为2 9。依次执行DELETE 3和DELETE 6(注意这时删除的是原始数组的元素7)后数组变为:

位置

1

2

3

4

5

6

7

8

元素

1

5

6

7

4

3

1

5

QUERY 2 8的结果为1 7。

 

【输入】

输入文件minmax.in第一行包含两个数n, m,表示原始数组的元素个数和操作的个数。第二行包括n个数,表示原始数组。以下m行,每行格式为1 k或者2 i j,其中第一个数为1表示删除操作,为2表示询问操作。

 

【输出】

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

 

【样例输入】

10 4

1 5 2 6 7 4 9 3 15

2 2 8

1 3

1 6

2 2 8

 

【样例输出】

2 9

1 7

 

【限制】

50%的数据满足1<=n, m<=104,删除操作不超过100个

100%的数据满足1<=n, m<=106, 1<=m<=106

对于所有的数据,数组中的元素绝对值均不超过109

评测数据:http://download.csdn.net/detail/yuyanggo/5300411

(若有在线评测网站的话,还请分享一下。)

解法:线段树

对于这道题,目前我本人还未找到一秒过的做法(测试数据都有10M那么大的存在),若有路过的神牛还有更好的思路,还请不吝赐教,万分感激。

思路:

(每次询问的k,i,j是现阶段中的元素位置,不是原序列中的。)

1.用tree[]来记录维护线段树,tree[p].sum记录p代表的区间内有多少个数,tree[p].max记录区间内的最大值,tree[p].min记录区间内的最小值。

2.用q记录所有的操作,用last记录记录最后一次询问操作的编号,然后在枚举操作时,就从1到last进行枚举即可

3.对于一个操作delete K,用po()函数求得现阶段序列中第k个元素在线段树中的节点编号j,将tree[j].min=inf(一个极大值),tree[j].max=-inf,tree[j].sum=0,就完成了对第k元素的操作(k不可能再有作为最小值或最大值出现的机会)。

4.对于操作QUERY i,j,用po2()函数求得现阶段序列中第i个元素在原序列中的位置,L=po2(i),R=po2(j),查询区间【L,R】中的最大最小值即得解。

5.用tree.sum记录区间元素总数因为每次查询时,问的都是现阶段序列中的位置i,用po2(i)函数来求得现阶段序列中第i个元素在原序列中的位置,这就需要用到tree.sum,po2()函数具体细节可以参考下面的代码。

6.用一个f【】数组子节点值有变化的节点进行标记(即被删除的点),然后就可以读入一段连续的删除操作,直到读到第一个询问操作时,才从上往下对线段树中子节点的值有变化的点进行更新

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
#define maxn (1000000+100)
#define inf 2000000000+100
using namespace std;
//tree记录维护线段树  q记录询问 
struct tnode{int min,max,sum;}tree[maxn*3];
struct node{int k,a,b;}q[maxn];
int n,m,last=0;
bool f[maxn*3];
//f[]记录子节点值有变化的节点 

void init()
{
  freopen("minmax.in","r",stdin);
  freopen("minmax.out","w",stdout);
}
//手写读入输出
inline int getin()
{
  int ans=0;bool sign=0;char tmp;
  do tmp=getchar();
  while(!isdigit(tmp) && tmp!='-');
  if(tmp=='-')sign=1,tmp=getchar();
  do ans=(ans<<3)+(ans<<1)+tmp-'0';
  while(isdigit(tmp=getchar()));
  return sign?-ans:ans;
}

inline tnode data(tnode a,tnode b)
{
  tnode k;
  k.min=min(a.min,b.min);
  k.max=max(a.max,b.max);
  k.sum=a.sum+b.sum;
  return k;
}
//初始建树时,遇到叶子节点就读入
void build_init(int p,int l,int r)
{
  if(l==r)
    {
      tree[p].min=tree[p].max=getin();
      tree[p].sum=1; return;
    }
  int m=(l+r)>>1,k=p<<1;
  build_init(k,l,m),build_init(k+1,m+1,r);
  tree[p]=data(tree[k],tree[k+1]);  
}

void readdata()
{
  n=getin(),m=getin();
  memset(f,0,sizeof(int)*(n*3));
  build_init(1,1,n);//初始建树

  int i;//读入操作
  for(i=1;i<=m;i++)
    {
      q[i].k=getin();q[i].a=getin();
      if(q[i].k==2)q[i].b=getin();
    } 
  for(i=m;i>0;i--)//得到last
    if(q[i].k==2){last=i;break;}   
}
//求得现阶段序列中第i个元素在线段树中的叶子节点编号 
inline int po(int i)
{
  int p=1,l=1,r=n,m,k,x=i;
  while(l<r)
    {
      m=(l+r)>>1,k=p<<1;
      if(x<=tree[k].sum)p=k,r=m;
      else p=k+1,l=m+1,x-=tree[k].sum;  
    }
  return p;  
}
//求得现阶段序列中第i个元素在原序列中的位置编号 
inline int po2(int i)
{
  int p=1,l=1,r=n,m,k,x=i;
  while(l<r)
    {
      m=(l+r)>>1,k=p<<1;
      if(x<=tree[k].sum)p=k,r=m;
      else p=k+1,l=m+1,x-=tree[k].sum;  
    }
  return l;  
}
//对子节点值有变化的节点进行染色标记 
inline void ranse(int i)
{
  int k=i>>1;
  while(k>1 && !f[k])f[k]=1,k=k>>1;
}
//处理子节点有变化的节点的sum值(即区间中有点被删除的点的sum值) 
inline void jian(int i)
{
  int k=i>>1;
  while(k>1){tree[k].sum--;k=k>>1;}
}
//对区间中子节点值有变化的点进行更新 
void build(int p,int l,int r)
{
  int m=(l+r)>>1,k=p<<1;
  if(f[k])build(k,l,m),f[k]=0;
  if(f[k+1])build(k+1,m+1,r),f[k+1]=0;
  tree[p]=data(tree[k],tree[k+1]);
}
//查找在原序列中区间【l,r】上的最大最小值 
tnode get(int p,int pl,int pr,int l,int r)
{
  if(l<=pl && pr<=r)return tree[p];
  int m=(pl+pr)>>1,k=p<<1;
  if(r<=m)return get(k,pl,m,l,r);
  if(l>m)return get(k+1,m+1,pr,l,r);
  return data(get(k,pl,m,l,r),get(k+1,m+1,pr,l,r));
} 

void work()
{
  int i,k,l,r;  bool q1=0;//q1记录上一个操作是否是删除操作 
  tnode t;
  for(i=1;i<=last;i++)
    if(q[i].k==1)
      {
        k=po(q[i].a);  q1=1;  ranse(k);  jian(k);
        tree[k].min=inf;tree[k].max=-inf;tree[k].sum=0;
      }
    else
      {
        if(q1)build(1,1,n),q1=0;
        l=po2(q[i].a),r=po2(q[i].b);
        t=get(1,1,n,l,r);
        printf("%d %d\n",t.min,t.max);
      }  
}

int main()
{
  init();
  readdata();
  work();
  return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值