Queue Sequence
Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 876 Accepted Submission(s): 217
Problem Description
There's a queue obeying the first in first out rule. Each time you can either push a number into the queue (+i), or pop a number out from the queue (-i). After a series of operation, you get a sequence (e.g. +1 -1 +2 +4 -2 -4). We call this sequence a queue sequence.
Now you are given a queue sequence and asked to perform several operations:
1. insert p
First you should find the smallest positive number (e.g. i) that does not appear in the current queue sequence, then you are asked to insert the +i at position p (position starts from 0). For -i, insert it into the right most position that result in a valid queue sequence (i.e. when encountered with element -x, the front of the queue should be exactly x).
For example, (+1 -1 +3 +4 -3 -4) would become (+1 +2 -1 +3 +4 -2 -3 -4) after operation 'insert 1'.
2. remove i
Remove +i and -i from the sequence.
For example, (+1 +2 -1 +3 +4 -2 -3 -4) would become (+1 +2 -1 +4 -2 -4) after operation 'remove 3'.
3. query i
Output the sum of elements between +i and -i. For example, the result of query 1, query 2, query 4 in sequence (+1 +2 -1 +4 -2 -4) is 2, 3(obtained by -1 + 4), -2 correspond.
Now you are given a queue sequence and asked to perform several operations:
1. insert p
First you should find the smallest positive number (e.g. i) that does not appear in the current queue sequence, then you are asked to insert the +i at position p (position starts from 0). For -i, insert it into the right most position that result in a valid queue sequence (i.e. when encountered with element -x, the front of the queue should be exactly x).
For example, (+1 -1 +3 +4 -3 -4) would become (+1 +2 -1 +3 +4 -2 -3 -4) after operation 'insert 1'.
2. remove i
Remove +i and -i from the sequence.
For example, (+1 +2 -1 +3 +4 -2 -3 -4) would become (+1 +2 -1 +4 -2 -4) after operation 'remove 3'.
3. query i
Output the sum of elements between +i and -i. For example, the result of query 1, query 2, query 4 in sequence (+1 +2 -1 +4 -2 -4) is 2, 3(obtained by -1 + 4), -2 correspond.
Input
There are less than 25 test cases. Each case begins with a number indicating the number of operations n (1 ≤ n ≤ 100000). The following n lines with be 'insert p', 'remove i' or 'query i'(0 ≤ p ≤ length (current sequence), 1 ≤ i, i is granted to be in the sequence).
In each case, the sequence is empty initially.
The input is terminated by EOF.
In each case, the sequence is empty initially.
The input is terminated by EOF.
Output
Before each case, print a line "Case #d:" indicating the id of the test case.
After each operation, output the sum of elements between +i and -i.
After each operation, output the sum of elements between +i and -i.
Sample Input
10 insert 0 insert 1 query 1 query 2 insert 2 query 2 remove 1 remove 2 insert 2 query 3 6 insert 0 insert 0 remove 2 query 1 insert 1 query 2
Sample Output
Case #1: 2 -1 2 0 Case #2: 0 -1
Source
Recommend
zhoujiaqi2010
题目:每次有三种操作
insert pos 表示在pos插入一个数,这个数是最小的正数没有在序列中出现的。而且还要在某个位置插入他的相反数remove num 表示把num以及-num去掉
query num 把num与-num之间的数求和输出
思路:
首先说一下insert操作对于需要插入的那个数,维护一个线段树表示区间l内可用的最小正整数就行了。这部分有很多写法,我比较喜欢线段树。
那么对于插入的正数,位置已经告诉了,就简单了,对于那个负数,要求的位置是最右边的满足条件的。
其实题目要求的就是正数的顺序和负数的是一样的。
那么如果当前数字i前面有n个正数,那么表示-i前面也有n个负数,而且又需要是最右边的
就是第 n+1个负数的左边,如果没有n+1个负数,那就看插到最后(所以代码中有一个判断)
那么怎么找第n+1个负数呢,只需要维护一个值,表示负数的个数就行了。
然后是remove操作,只要记录num以及-num在Splay中的编号就可以了。
那么 就可以很轻松的通过编号找到结点,删除
我存的是编号,那么在删除的时候,先旋转至根,找到他的左边有多少个数,这样得到他的位置,就好处理了
最后是query操作,由于 我们存了编号
那么通过Splay很快能找到中间的区间,维护一个区间和就OK了
第一次写splay的题。研究了好一阵才理解的splay的操作。而研究别人的代码就慢了。毕竟第一次自己写这种数据结构的代码比较难。所以还是从学习别人代码开始吧。慢慢就有自己的风格了。加油!
详细见代码:
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=100100;
const int INF=0x3f3f3f3f;
ll sum[maxn<<1];//子结点的和
int sz[maxn<<1],pre[maxn<<1],val[maxn<<1],ps[maxn<<1][2],nag[maxn<<1];//sz记录子树规模,pre记录父结点,val存结点值。ps[k][0],ps[k][1]k的左右儿子。nag子树负数个数
int mi,mo,root,s[maxn<<1];//mi回收内存。mo分配内存。root为根。s为内存池
int pp[maxn][2];//记录插入点在splay中的编号方便查找和求和
int minn[maxn<<4];//记录最小的没有使用的整数
void Treaval(int x)//Debug部分打出来非常清楚
{
if(x)
{
Treaval(ps[x][0]);
printf("结点%2d:左儿子 %2d 右儿子 %2d 父结点 %2d size = %2d ,val = %2d \n",x,ps[x][0],ps[x][1],pre[x],sz[x],val[x]);
Treaval(ps[x][1]);
}
}
void Debug()
{
printf("root:%d\n",root);
Treaval(root);
}
void Newnode(int &r,int k,int f)
{
if(mi)//注意mi记录可用空间大小但里面没有空间所以要先减减。不然会出错
r=s[--mi];
else
r=mo++;
ps[r][0]=ps[r][1]=0;
nag[r]=k<0;
sum[r]=k;
pre[r]=f;
sz[r]=1;
val[r]=k;
}
void Push_up(int x)
{
int l,r;
if(x==0)
return;
l=ps[x][0],r=ps[x][1];
sz[x]=sz[l]+sz[r]+1;
nag[x]=nag[l]+nag[r]+(val[x]<0);
sum[x]=sum[l]+sum[r]+val[x];
}
void Init()//初始化很重要!
{
mi=root=0;
mo=1;
ps[0][0]=ps[0][1]=pre[0]=sz[0]=sum[0]=nag[0]=0;
val[0]=0;//建一个虚拟根节点。做标记用。pre==0说明就到根了
Newnode(root,0,0);//建真正的根和右儿子。必须加这两个虚拟结点。这样区间操作起来才方便
Newnode(ps[root][1],0,root);
Push_up(ps[root][1]);
Push_up(root);
}
void Rotate(int x,int w)//旋转。w为旋转方式。0为左旋。1为右旋。结点为左子树就右旋。右子树就左旋
{
int y=pre[x];
ps[y][!w]=ps[x][w];
pre[ps[x][w]]=y;
if(pre[y])
ps[pre[y]][ps[pre[y]][1]==y]=x;
pre[x]=pre[y];
ps[x][w]=y;
pre[y]=x;
Push_up(y);
}
void Splay(int r,int goal)//goal为目标父结点
{
int y,w;
while(pre[r]!=goal)
{
if(pre[pre[r]]==goal)
Rotate(r,ps[pre[r]][0]==r);
else
{
y=pre[r];
w=(ps[pre[y]][0]==y);
if(ps[y][w]==r)
{
Rotate(r,!w);
Rotate(r,w);
}
else
{
Rotate(y,w);
Rotate(r,w);
}
}
}
Push_up(r);
if(goal==0)//旋转到根时更换根结点
root=r;
}
int Get_kth(int r,int k)//取第k个数的标号
{
int t=sz[ps[r][0]]+1;
if(t==k)
return r;
if(t>k)
return Get_kth(ps[r][0],k);
else
return Get_kth(ps[r][1],k-t);
}
int Insert(int pos,int k)//在pos位置插入一个数
{
Splay(Get_kth(root,pos),0);//因为前面有一个虚拟结点所以实际插的位置为pos+1
Splay(Get_kth(root,pos+1),root);
Newnode(ps[ps[root][1]][0],k,ps[root][1]);
Push_up(ps[root][1]);
Push_up(root);
return ps[ps[root][1]][0];
}
void Delete(int r)//删除结点
{
Splay(r,0);//先把该点旋转到根
int pos=sz[ps[r][0]];//获取它前面有多少个数
Splay(Get_kth(root,pos),0);
Splay(Get_kth(root,pos+2),root);
s[mi++]=ps[ps[root][1]][0];
ps[ps[root][1]][0]=0;
Push_up(ps[root][1]);
Push_up(root);
}
int findn(int x,int n)//寻找第n+1个负数的位置(前面有多少个数)
{
int l=ps[x][0],r=ps[x][1];
if(nag[l]==n&&val[x]<0)
{
Splay(x,0);
return sz[ps[root][0]];
}
else if(nag[l]>=n+1)
return findn(l,n);
else
return findn(r,n-nag[l]-(val[x]<0));
}
ll Query(int k)//求和和轻松
{
Splay(pp[k][0],0);
Splay(pp[k][1],root);
return sum[ps[ps[root][1]][0]];
}
void btree(int L,int R,int k)//建线段树。维护最小的没被使用的正整数
{
int ls,rs,mid;
mid=(L+R)>>1;
ls=k<<1;
rs=k<<1|1;
minn[k]=L;
if(L==R)
return;
btree(L,mid,ls);
btree(mid+1,R,rs);
}
void update(int L,int R,int k,int num,int op)
{
int ls,rs,mid;
if(L==R)
{
minn[k]=op;
return;
}
mid=(L+R)>>1;
ls=k<<1;
rs=k<<1|1;
if(num<=mid)
update(L,mid,ls,num,op);
else
update(mid+1,R,rs,num,op);
minn[k]=min(minn[ls],minn[rs]);
//printf("%d->%d:%d\n",L,R,minn[k]);
}
void addin(int num,int pos)
{
pos++;//注意题目是从0开始。所以后移一个
pp[num][0]=Insert(pos,num);
Splay(pp[num][0],0);
int p,m=sz[ps[root][0]]-nag[ps[root][0]]-1;//m计算出插入位置前面有多少个正数。减为虚拟结点
if(nag[root]<=m)//没有第n+1个负数只能插在最后面了。
{
p=sz[root]-2+1;//减两个虚拟结点的下一个位置
pp[num][1]=Insert(p,-num);
}
else
{
p=findn(root,m);
pp[num][1]=Insert(p,-num);
}
}
int main()
{
int n,op,i,t,cas=1;
char com[15];
while(~scanf("%d",&n))
{
printf("Case #%d:\n",cas++);
btree(1,n,1);
Init();
for(i=0;i<n;i++)
{
scanf("%s%d",com,&op);
switch(com[0])
{
case 'i':
t=minn[1];
update(1,n,1,t,INF);
addin(t,op);
//Debug();
break;
case 'q':printf("%I64d\n",Query(op));
break;
case 'r':
Delete(pp[op][0]);
Delete(pp[op][1]);
update(1,n,1,op,op);
//Debug();
}
}
}
return 0;
}