洛谷题目如下:
题目描述
一个学校里老师要将班上 N 个同学排成一列,同学被编号为 1∼N,他采取如下的方法:
-
先将 11 号同学安排进队列,这时队列中只有他一个人;
-
2∼N 号同学依次入列,编号为 i 的同学入列方式为:老师指定编号为 i 的同学站在编号为 1∼(i−1) 中某位同学(即之前已经入列的同学)的左边或右边;
-
从队列中去掉 M 个同学,其他同学位置顺序不变。
在所有同学按照上述方法队列排列完毕后,老师想知道从左到右所有同学的编号。
输入格式
第一行一个整数 N,表示了有 N 个同学。
第 2∼N 行,第 i 行包含两个整数 k,p,其中 k 为小于 i 的正整数,p 为 00 或者 11。若 p 为 00,则表示将 i 号同学插入到 k 号同学的左边,p 为 11 则表示插入到右边。
第 N+1 行为一个整数 M,表示去掉的同学数目。
接下来 M 行,每行一个正整数 x,表示将 x 号同学从队列中移去,如果 x 号同学已经不在队列中则忽略这一条指令。
输出格式
一行,包含最多 N 个空格隔开的整数,表示了队列从左到右所有同学的编号。
输入输出样例
输入 #1复制
4 1 0 2 1 1 0 2 3 3
输出 #1复制
2 4 1
说明/提示
【样例解释】
将同学 22 插入至同学 11 左边,此时队列为:
2 1
将同学 33 插入至同学 22 右边,此时队列为:
2 3 1
将同学 44 插入至同学 11 左边,此时队列为:
2 3 4 1
将同学 33 从队列中移出,此时队列为:
2 4 1
同学 33 已经不在队列中,忽略最后一条指令
最终队列:
2 4 1
【数据范围】
对于 20%20% 的数据1≤N≤10。
对于 40%40% 的数据,1≤N≤1000。
对于 100%100% 的数据,1<M≤N≤105。
这道题目考察链表,我的第一反应就是建立一个双向链表,然后遍历链表进行增删,最后输出
然后……
悲剧发生了
由于每次删除链表都要先把辅助指针放到头结点上,而且每次增删都要遍历一遍链表,这就导致了我写出了时间复杂度为o(2)的垃圾错误超时代码(TELwwwww)
#include<stdio.h>
#include<stdlib.h>
typedef struct lnode
{
int sign;
struct lnode* left, * right;
}lnode,*linklist;
void init(linklist* l)
{
*l = (linklist)malloc(sizeof(lnode));
(*l)->left = NULL;
(*l)->right = NULL;
}
linklist setfirst(linklist* p)
{
linklist q;
q=(*p);
while (q->left->left != NULL)
{
q = q->left;
}
return q;
}
void creatlist(linklist* l,int peopnum)
{
int sub,before,count=1,j;
linklist p,q;
p = (linklist)malloc(sizeof(lnode));
for (int i = 2; i <=peopnum;i++)
{
q = setfirst(l);
scanf("%d", &before);
for ( j = 1; j <= count; j++)
{
if (q->sign == before)
break;
q=q->right;
}
scanf("%d",&sub);
if (sub == 0)
{
p->right = q;
p->left = q->left;
q->left->right=p;
q->left = p;
p->sign = i;
}
if (sub == 1)
{
p->left = q;
p->right = q->right;
q->right->left=p;
q->right = p;
p->sign = i;
}
p = (linklist)malloc(sizeof(lnode));
count++;
}
}
void del( int peopnum,int delsign,linklist *p)
{
linklist q;
q = (*p);
for (int i = 1; i <= peopnum; i++)
{
q=q->right;
if (q->sign == delsign)
{
q->sign = -1;
}
}
}
int main()
{
linklist l_r,p,q,l_l;
int peopnum,delnum,delsign;
scanf("%d", &peopnum);
init(&l_r);
init(&l_l);
//建立两个空的头结点更好操作
//分别连在首元结点的两边
l_r->sign = -1;
l_l->sign=-1;
q=(linklist)malloc(sizeof(lnode));
q->sign=1;
q->right=l_r;
q->left=l_l;
l_r->left=q;
l_l->right=q;
creatlist(&q, peopnum);
p = setfirst(&q);
scanf("%d", &delnum);
for (int i = 1; i <= delnum; i++)
{
scanf("%d", &delsign);
del(peopnum, delsign, &p);
}
for (int i = 1; i <= peopnum; i++)
{
if (p->sign >= 0)
{
printf("%d ", p->sign);
p = p->right;
}
else
p = p->right;
}
return 0;
}
而且我也想不出什么优化时间复杂度的方案了(大佬求指点)
于是我看了看神犇的代码,我这个蒟蒻看了才知道,链表还能这么玩??
下面是我偷师自成的代码
#include<stdio.h>
#include<stdlib.h>
struct T
{
int left;
int right;
int del;
};
struct T t[100010]= {0}; //初始化
void add(struct T* t,int before, int sub,int sign)
{
if (sub == 0)
{
t[sign].right = before;
t[sign].left = t[before].left;
t[t[before].left].right = sign;
t[before].left = sign;
}
else
{
t[sign].left = before;
t[sign].right = t[before].right;
t[t[before].right].left = sign;
t[before].right = sign;
}
}
void print(struct T* t, int peopnum)
{
for (int i =t[0].right; i; i=t[i].right)
{
if (t[i].del==0)
printf("%d ",i );
}
}
int main()
{
int peopnum,sub,delnum,delsign,before;
scanf("%d", &peopnum);
t[0].right=0;
t[0].left=0;
add(t,0,1,1);
for (int i = 2; i <= peopnum; i++)
{
scanf("%d", &before);
scanf("%d", &sub);
add(t, before, sub, i);
}
scanf("%d", &delnum);
while(delnum--)
{
scanf("%d", &delsign);
t[delsign].del=1;
}
print(t,peopnum);
return 0;
}
他是以数组为模型的链表,这样就完美实现了快速通过下标删除结点的操作,时间复杂度直接下降到常数级别(666666)
这让我不禁思考,我总是惯性的以为链表就是用结构体指针来实现的,但其实数组链表反而实现更轻松,用int也可以实现指针的操作。
看来脱离自己的思维惯性确实很难啊