动态最值
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;
}