[ZJOI2007]报表统计
题目描述
Q的妈妈是一个出纳,经常需要做一些统计报表的工作。今天是妈妈的生日,小Q希望可以帮妈妈分担一些工作,作为她的生日礼物之一。
经过仔细观察,小Q发现统计一张报表实际上是维护一个非负整数数列,并且进行一些查询操作。
在最开始的时候,有一个长度为N的整数序列,并且有以下三种操作:
INSERT i k:在原数列的第i个元素后面添加一个新元素k;如果原数列的第i个元素已经添加了若干元素,则添加在这些元素的最后(见下面的例子)
MIN_GAP:查询相邻两个元素的之间差值(绝对值)的最小值
MIN_SORT_GAP:查询所有元素中最接近的两个元素的差值(绝对值)
例如一开始的序列为
5 3 1
执行操作INSERT 2 9将得到:
5 3 9 1
此时MIN_GAP为2,MIN_SORT_GAP为2。
再执行操作INSERT 2 6将得到:
5 3 9 6 1
注意这个时候原序列的第2个元素后面已经添加了一个9,此时添加的6应加在9的后面。这个时候MIN_GAP为2,MIN_SORT_GAP为1。
于是小Q写了一个程序,使得程序可以自动完成这些操作,但是他发现对于一些大的报表他的程序运行得很慢,你能帮助他改进程序么?
输入格式:
第一行包含两个整数N,M,分别表示原数列的长度以及操作的次数。
第二行为N个整数,为初始序列。
接下来的M行每行一个操作,即“INSERT i k”,“MIN_GAP”,“MIN_SORT_GAP”中的一种(无多余空格或者空行)。
输出格式:
对于每一个“MIN_GAP”和“MIN_SORT_GAP”命令,输出一行答案即可。
输入样例#1:
3 5
5 3 1
INSERT 2 9
MIN_SORT_GAP
INSERT 2 6
MIN_GAP
MIN_SORT_GAP
输出样例#1:
2
2
1
说明:
对于30%的数据,N ≤ 1000 , M ≤ 5000
对于100%的数据,N , M ≤500000
对于所有的数据,序列内的整数不超过5*108。
时限2s
题解:
其实这道题有很多种解法
先说操作1
①.用splay差分,插入时删除插入位置的两边的差,插入插入的值与俩边的差,查询时找最小值就可以了
②.用堆维护,写一个支持删除的堆就可以了。
③.线段树维护,如果我们把每个原序列中的数字和它后面插入的那些数字看成一组的话。我们会发现,组内MIN_GAP是越来越小的,而且是不可逆的。唯一会发生变化的是(有可能由小变大,也可能由大变小)两组之间的GAP值。所以我们需要动态维护每两组之间的GAP值(点修改),并且要能随时查询最小值。可以用线段树来维护。
操作2
①.用splay维护,由于插入的时候一定经过与当前节点差值最小的点(先自行证明,下面有解释)那么每次插入的时候更新答案就行了。
②.还是用splay维护,思路更简单每次插入后(已经在根上)查找最近的元素就可以了
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int max_n = 600001;
const int inf = 1e9;
struct point
{
int val,last,min_ans;
}p[max_n];
struct node
{
int r,l;
int min;
}tree[max_n*4];
struct splay_node
{
int val,cnt;
splay_node *ch[2];
splay_node *pre;
void set_son(splay_node *child,int wh);
int get_wh();
}pool[max_n*2],*null,*root;
inline void splay_node::set_son(splay_node *child,int wh)
{
ch[wh]=child;
if(child!=null) child->pre=this;
}
inline int splay_node::get_wh()
{
return pre->ch[0]==this ? 0 : 1;
}
char s[15];
int n,m,x,y,min_s,min_a,tot;
inline int abs(int a)
{
return a>=0 ? a : -a;
}
void init()
{
null=pool;
null->val=0; null->cnt=0;
null->ch[0]=null->ch[1]=null->pre=null;
root=null; tot=0;
min_a=inf; min_s=inf;
}
splay_node *get_new(int val)
{
splay_node *newnode=pool+ ++tot;
newnode->val=val;
newnode->cnt=1;
newnode->ch[0]=newnode->ch[1]=newnode->pre=null;
return newnode;
}
inline void update(int now)
{
tree[now].min=min(tree[now<<1].min,tree[(now<<1)+1].min);
}
inline void build(int now,int l,int r)
{
tree[now].l=l;
tree[now].r=r;
if(l==r)
{
tree[now].min=inf;
return;
}
int mid=(l+r)>>1;
build(now<<1,l,mid);
build((now<<1)+1,mid+1,r);
}
inline void change(int now,int l,int r,int val)
{
int lr=tree[now].l,rr=tree[now].r;
if(lr>=l && rr<=r)
{
tree[now].min=val;
return;
}
int mid=(lr+rr)>>1;
if(l<=mid)
change(now<<1,l,r,val);
if(r>mid)
change((now<<1)+1,l,r,val);
update(now);
}
inline void rotate(splay_node *now)
{
splay_node *fa=now->pre,*grand=now->pre->pre;
int wh=now->get_wh();
fa->set_son(now->ch[wh^1],wh);
now->set_son(fa,wh^1);
now->pre=grand;
if(grand!=null) grand->ch[grand->ch[0]==fa ? 0 : 1]=now;
}
inline void splay(splay_node *now,splay_node *tar)
{
for(; now->pre!=tar; rotate(now))
if(now->pre->pre!=null)
now->pre->get_wh()==now->get_wh() ? rotate(now->pre) : rotate(now);
if(tar==null) root=now;
}
inline void insert(int val)
{
splay_node *now=root,*last=null,*newnode=get_new(val);
while(now!=null)
{
last=now;
min_s=min(min_s,abs(last->val-val));
if(val==now->val)
{
now->cnt++;
splay(now,null);
return;
}
if(val<now->val)
now=now->ch[0];
else
now=now->ch[1];
}
if(last==null)
{
root=newnode;
return;
}
else
min_s=min(min_s,abs(last->val-val));
if(last->val>val)
last->set_son(newnode,0);
else
last->set_son(newnode,1);
splay(newnode,null);
}
int main()
{
// freopen("form.in","r",stdin);
// freopen("form.out","w",stdout);
scanf("%d%d",&n,&m);
init(); build(1,1,n);
for(int i=1; i<=n; ++i)
{
scanf("%d",&p[i].val);
change(1,i-1,i-1,abs(p[i].val-p[i-1].val));
insert(p[i].last=p[i].val);
}
for(int i=1; i<=m; ++i)
{
scanf("%s",s);
if(s[0]=='I')
{
int temp;
scanf("%d%d",&x,&y);
if(abs(p[x].last-y)<min_a)
min_a=abs(p[x].last-y);
change(1,x,x,abs(p[x+1].val-y));
p[x].last=y;
insert(y);
}
else if(s[4]=='G')
printf("%d\n",min(min_a,tree[1].min));
else if(s[4]=='S')
printf("%d\n",min_s);
}
return 0;
}
--------------------------------------分割线----------------------------------
对于前面结论的证明:
对于这样一颗splay,如果往左跑,那么右儿子与插入节点的权值的差一定小于父亲节点和左儿子的(很显然是吧),那么就很明显了。