hdu 4441 Queue Sequence(线段树&splay数据结构的好题!)

本文介绍了一种名为队列序列的问题解决方法,包括插入、删除和查询操作的具体实现细节。通过对队列序列进行操作,文章展示了如何使用线段树和Splay树来高效地处理这些操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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.
 

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.
 

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.
 

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值