P1160 队列安排(STL-list,splay,树,链表,模拟链表)

传送门

题解:使用STL中的list

迭代器的类型是list<Tp>::iterator(这里模板参数Tp需要与你操作的链表一致)。

迭代器的用法和指针有些像,可以用*运算符访问内部的元素,++和--运算符可以将它后移或前移一位(建议写成前置形式),用==和!=运算符进判断两个迭代器所指的位置是否一致。但要注意:list的迭代器不支持it += x或it1 - it2这样的运算,也不支持<,<=等运算符。

begin()成员函数返回指向头部元素的迭代器。

end()成员函数返回指向末尾位置的迭代器。这个“末尾位置”指的是最后一个元素再往后一位,也就是说end()所指的位置不包含有效元素,它相当于一个虚设的节点。这样设计是为了满足C++标准库表示区间时左闭右开的惯例。

insert(it, val)成员函数用于在链表中插入元素。it为该链表的一个迭代器,val为待插入的值,插入后val位于it所指位置的前一位。返回值为一个迭代器,表示val插入到了哪个位置。

remove(it)成员函数用于删除某个迭代器所指的节点。注意在删除之后it就失效了,除非给it重新赋值,否则对它的任何操作都会导致错误!

除上述主要操作以外,list还提供了其他一些实用的成员函数:size()返回链表内元素的个数,empty()判断链表是否为空,remove(val)用于移除所有值为val的节点,以及作为成员函数的sort()和unique()。(注意sort(myList.begin(), myList.end())是错误的写法)

附上代码:


#include<bits/stdc++.h>

using namespace std;

using Iter=list<int>::iterator;

const int maxn=1e5+10;
Iter pos[maxn];
list<int>quelist;
bool erased[maxn];
int n;

void buildqueue()
{
    quelist.push_front(1);
    pos[1]=quelist.begin();
    for(int i=2;i<=n;i++){
        int k,p;
        scanf("%d%d",&k,&p);
        if(p==0){
            pos[i]=quelist.insert(pos[k],i);
        }else{
            auto nextIter=next(pos[k]);
            pos[i]=quelist.insert(nextIter,i);
        }
    }
    int m;
    scanf("%d",&m);
    int x;
    for(int i=1;i<=m;i++){
        scanf("%d",&x);
        if(!erased[x]){
            quelist.erase(pos[x]);
        }
        erased[x]=true;
    }
}

int main()
{
    scanf("%d",&n);
    buildqueue();
    bool first=true;
    for(list<int>::iterator iter=quelist.begin();iter!=quelist.end();iter++){
        if(!first){
            printf(" ");
        }
        first=false;
        printf("%d",*iter);
    }
    printf("\n");
    return 0;
}

 题解二:模拟链表

题解:使用数组进行模拟链表,和邻接表非常像

#include<cstdio>
#include<cstring>
int a[100010][3],n,m;
//a[i][2]表示学号为i的同学右边同学的学号
//a[i][3]表示学号为i的同学左边同学的学号
int main()
{
    scanf("%d",&n);
    int j=1;
    memset(a,0,sizeof(a));
    a[1][1]=1;
    for(int i=2;i<=n;i++)
    {
        int x,y;
        scanf("%d %d",&x,&y);
        a[i][1]=i;
        if(y==0)
        //插入左边
        {
            a[a[x][3]][2]=i;
            a[i][2]=x;
            a[i][3]=a[x][3];
            a[x][3]=i;
            if(x==j) j=i;//最左端
            //比较麻烦,要改链表
        }
        else
        //插入右边
        {
            a[i][2]=a[x][2];
            a[a[x][2]][3]=i;
            a[x][2]=i;
            a[i][3]=x;
        }
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        int x;
        scanf("%d",&x);
        if(a[x][1]!=0)
        //该同学还在
        {
            a[x][1]=0;
            //踢掉……(好可怜)
            a[a[x][3]][2]=a[x][2];
            a[a[x][2]][3]=a[x][3];
            n--;
            if(x==j) j=a[x][2];
        }
    }
    int i=1,x=j;
    while(i<=n)
    {
        printf("%d ",a[x][1]);
        x=a[x][2]; i++;
    }
    return 0;
}

题解三:使用底层实现的双向链表

需要做的一件事,就是设置好已经在队列中的1号同学的数据信息,由于在程序刚刚开始运行时,队列中只有1号同学一个人,为了方便后面的*插入*和*删除*的操作,因此,1号同学的前面和后面都是1号同学

 que[1].back = &que[1]; que[1].front = &que[1];

附上代码:


#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

struct queueDat{
    int ID;            //该同学的号码
    queueDat *front = NULL, *back = NULL;
}que[100001];

queueDat *head = &que[1];

void _cut(int ID)
{
    queueDat *cut = &que[ID];
    if (cut->ID == head->ID){
        head = cut->back;
    }
    cut = cut->front;
    cut->back = cut->back->back;
    cut = cut->back;
    cut->front = cut->front->front;
}

void _add(int num, int ID, bool back)
{
    queueDat *find = &que[ID], *add = &que[num];
    if (back){        //插入在后面
        add->front = find;
        add->back = find->back;
        find->back = add;
        find = find->back->back;
        find->front = add;
        return;
    }else{            //插入在前面
        add->back = find;
        add->front = find->front;
        find = find->front;
        find->back = add;
        find = find->back->back;
        find->front = add;
        if (ID == head->ID){
            head = add;
        }
    }
}

int main()
{
    bool inQueue[100001];
    memset(inQueue, false, sizeof(inQueue));
    inQueue[1] = true;
    for (int i = 1; i < 100001; i++){
        que[i].ID = i;        //que[i]对应i号同学
    }
    que[1].back = &que[1];
    que[1].front = &que[1];
    int totStudents, a, b;
    cin >> totStudents;            //同学总数
    for (int i = 2; i <= totStudents; i++){    //i代表同学的号码
        inQueue[i] = true;                    //确认i号同学在队列当中
        cin >> a >> b;                        //插入在a同学的 左边或右边
        _add(i, a, ((b == 0) ? false : true));
    }
    cin >> totStudents;
    for (int i = 0; i < totStudents; i++){
        cin >> a;
        if (inQueue[a] == true){
            inQueue[a] = false;
            _cut(a);
        }
    }
    b = head->ID;
    do{
        cout << head->ID << " ";
        head = head->back;
    } while (b != head->ID);
    cout << endl;
    return 0;
}

题解四:使用树,太机智了

附上代码:


#include<iostream>
#include<cstdio>

using namespace std;

const int maxn=101000;

struct node{
    int lc,rc,v;
};
node d[maxn];

void dfs(int x)
{
    if(x==-1){
        return ;
    }
    dfs(d[x].lc);
    if(d[x].v==0){
        printf("%d ",x);
    }
    dfs(d[x].rc);
}

int main()
{
    int n,m;
    scanf("%d",&n);
    d[1].v=0;
    d[1].lc=d[1].rc=-1;
    for(int i=2;i<=n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        d[i].lc=d[i].rc=-1;
        d[i].v=0;
        if(y==0){
            if(d[x].lc!=0){
                d[i].lc=d[x].lc;
                d[x].lc=i;
            }else{
                d[x].lc=i;
            }
        }else{
            if(d[x].rc!=0){
                d[i].rc=d[x].rc;
                d[x].rc=i;
            }else{
                d[x].rc=i;
            }
        }
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int x;
        scanf("%d",&x);
        d[x].v=1;
    }
    dfs(1);
    return 0;
}

题解五:使用splay

先加入两个哨兵结点0和n+1

插入操作 将编号为k的同学旋转到根

若p==1,编号k的下一位同学旋转到根的右子树, 然后将带插入的结点直接插入根的右子树的左子树

若p==0,编号k的上一位同学旋转到根的左子树, 然后将带插入的结点直接插入根的左子树的右子树

删除操作

找到编号x的同学的上一个同学旋转到根, 下一个同学旋转到根的右子树, 然后直接删除根的右子树的左子树

对于重复的开一个judge数组记录就好了

最后中序遍历输出

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}

const int maxn=200010;
int n,m;
int ch[maxn][2],fa[maxn],rt=1;
int size[maxn],v[maxn],pos[maxn],sz;
int judge[maxn];

void print(int p)
{
    if(!p) return;
    print(ch[p][0]);
    if(v[p]!=0&&v[p]!=n+1)printf("%d ",v[p]);
    print(ch[p][1]);
}

void update(int p)
{
    size[p]=size[ch[p][0]]+size[ch[p][1]]+1;
}

void rotate(int& p,int x) 
{ 
    int y=fa[x],z=fa[y]; 
    int d=(ch[y][0]==x);
    if(y==p) p=x; 
    else if(ch[z][0]==y) ch[z][0]=x; 
    else ch[z][1]=x;  
    fa[y]=x; fa[ch[x][d]]=y; fa[x]=z;  
    ch[y][d^1]=ch[x][d]; ch[x][d]=y;   
    update(y); update(x);  
} 

void splay(int& p,int x) 
{   
    while(x!=p) 
    {
        int y=fa[x],z=fa[y];
        if(y!=p) 
        {
            if((ch[y][0]==x)^(ch[z][0]==y)) rotate(p,x);
            else rotate(p,y);
        }
        rotate(p,x);
    }
}

void insert(int x)
{
    v[++sz]=x; pos[x]=sz;
    size[sz]=1; ch[sz][1]=ch[sz][0]=0;
    if(sz>1)
    {
        ch[sz-1][1]=sz; fa[sz]=sz-1;
        splay(rt,sz);
    }
}

int find(int p,int k)
{ 
    int ss=size[ch[p][0]]; 
    if(k==ss+1) return p;    
    if(k<=ss) return find(ch[p][0],k);
    else return find(ch[p][1],k-ss-1);  
}  

void ins(int x,int d,int k)//k号同学在x号同学的右(p==1)/左(p==0)边
{
    splay(rt,x);int y;
    if(d==1) y=find(rt,size[ch[x][0]]+2); //插入右边,找到x的下一个
    else y=find(rt,size[ch[x][0]]);//插入左边,找到x的上一个
    splay(ch[x][d],y);

    v[++sz]=k; pos[k]=sz;//插入
    size[sz]=1; ch[sz][1]=ch[sz][0]=0;
    fa[sz]=y; ch[y][d^1]=sz;
    update(y); update(x);
}

void del(int x)
{
    splay(rt,x);
    int y=find(rt,size[ch[x][0]]); //x的上一个同学
    int z=find(rt,size[ch[x][0]]+2); //x的下一个同学
    splay(rt,y); splay(ch[y][1],z);//上一个伸展到根,下一个伸展到根的右子树
    fa[ch[z][0]]=0; ch[z][0]=0;//删除
    update(z); update(y);
}

int main()
{
    n=read();
    insert(0); insert(n+1);//哨兵结点

    ins(pos[0],1,1);//先让1号入队
    for(int i=2;i<=n;++i)
    {
        int k=read(),p=read();
        ins(pos[k],p,i);
    }

    m=read();
    while(m--)
    { 
        int x=read(); if(judge[x]) continue; 
        judge[x]=1;
        del(pos[x]);
    }

    print(rt);
    return 0;
    //niiick
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值