题目描述
背景:
小军的军训进行到了一半了,今天军训教官搞了一波突然袭击,进行查寝。
提前了解到查寝消息的小军准备进行一波整理归纳,来使自己的寝室变得更加整洁。具体来说,小军有n件物品,放在n个盒子里,第i个盒子有物品i,小军会进行m次整理,第i次整理,小军会依次在第x个盒子顶拿走物品放入第y个盒子内,直至第x个盒子完全搬空。比如第1个盒子自顶向下有物品1、2,第2个盒子有物品3,将盒子1内的物品搬入盒子2内后结果是: 第1个盒子没有物品,第2个盒子自顶向下是2、1、3
现在,小军告诉你n还有m次操作具体是什么,你能告诉他最后每个盒子内有几个物品,他们具体是什么么?
输入:
一个正整数n代表盒子和物品数,一个正整数m代表整理归纳的次数
接下来m行输入,一行两个正整数x y,代表用上述的方法将盒子x的物品搬到盒子y里
1≤n≤10^5, 1≤m≤10^6, 1≤x,y≤n
题目保证x != y
输出:
有n行输出
第i行,先输出一个正整数k,表示第i个盒子内的物品数,接下来输出n个数,表示第i个盒子自顶向下的物品标号
注意:
行末无空格,文末有回车。
第一印象
我第一眼看到题的时候,觉得这题很简单,只要开一个栈的数组,用栈去模拟即可,满心欢喜提交上去,结果有错误提示:bad_alloc,查了一下,是因为内存不够,开不出来有1e5个stack的数组。不过貌似即使能开出来,用栈去模拟,一个一个地pop和push还是会超时(毕竟m最大有1e6)。
再思考
后来我想到,移动盒子的物品时,并不需要一个一个地移动,只需要将盒x中物品倒置在y盒中,直接接上就行了。像这样只在一组数据的两头进行连接操作,很明显是链表的的特征。同时考虑到链表的两头可能都需要连接物品,所以要采用双向链表去模拟每一个盒子中存放物品的情况。
于是我先构造了一种结构类型node来实现双向链表,node里面有指向上一个和下个节点的指针pre和next,以及代表物品序号的整型数int。
所遇问题
- 在连接和输出时,如何找到一个盒子的链表的最上端node和最下端node?对于此,我开了两个指针数组high和low,high[i]和low[i]分别指向第i个盒子最上端node和最下端node。
- 连接操作如何实现?简单来说,就是把第x个盒子的最上端node和第y个盒子的最上端node连接起来,然后更新high[y], low[y], high[x], low[x]。对于x空或y空的情况还要进行特判,避免在使用指针的过程中造成无效内存引用(RE)。实际操作的过程还是很复杂的,需要考虑到各个方面,并注意用if else判断时代码的逻辑和我们所想的是否一致。
- 连接时需要考虑接的方向吗(用next还是pre指针去指向要接的节点)?这个我思考了很久,结果是:实际操作过程会很复杂,根本无法保证一个链表的方向。所以我们在连接时,只要接上了就行,不用考虑是用x盒最上端node的pre指针还是next指针去指向y盒的最上端node。
- 那么,输出一个盒子中的物品时该如何遍历链表呢?对于一个非空的盒子,我们将p指针初始化为high[i],显然我们不能再让p一直访问p的next结点或pre结点,那么,我们该如何指定p所访问的方向呢?显然每一个物品序号只会被输出一次,因此对于每一个node,我又在其中添加了一个bool变量isvisit,来标志该结点是否被访问过。通过此,我们就可以知道哪个节点被访问过,让p向未访问的方向移动即可。
- 此外,我又开了一个num数组,num[i]表示第i个盒子中物品数,在实行操作时不断更新就ok了。
时间复杂度
这样做的话,我们可以将时间复杂度降至O(max(m,n))级别,对于n<=1e5和m<=1e6,显然可行。
代码实现
很多功能我都放在函数里
#include <iostream>
#define maxn 100010
struct node{
node *pre;
int id;
node *next;
bool isvisit;
};
node *high[maxn]={NULL},*low[maxn]={NULL};
int num[maxn];
void list_cat(int x,int y);
void node_create(int i);
void box_visit(int i);
int main()
{
int m,n,x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
node_create(i);
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
list_cat(x,y);
}
for(int i=1;i<=n;i++)
{
box_visit(i);
}
return 0;
}
void list_cat(int x,int y)
{
if(high[x]==NULL) ;
else
{
if(high[x]->pre==NULL)
{
high[x]->pre=high[y];
}
else
{
high[x]->next=high[y];
}
if(high[y]!=NULL)
{
if(high[y]->pre==NULL) high[y]->pre=high[x];
else high[y]->next=high[x];
}
else
{
low[y]=high[x];
}
high[y]=low[x];
high[x]=NULL;
low[x]=NULL;
num[y]+=num[x];
num[x]=0;
}
}
void node_create(int i)
{
node *em=new node;//创建链表节点
em->id=i;
em->next=NULL;
em->pre=NULL;
high[i]=em;
low[i]=em;
num[i]=1;
}
void box_visit(int i)
{
printf(num[i]==0?"%d\n":"%d ",num[i]);
node *p=high[i];
for(int j=1;j<=num[i];j++)
{
printf(j==num[i]?"%d\n":"%d ",p->id);
p->isvisit=true;
bool flag=false;
if(p->next!=NULL && !(p->next)->isvisit)
{
p=p->next;
flag=true;//flag用于记录p在这里有没有向next访问
}
if(!flag && p->pre!=NULL && !(p->pre)->isvisit)
{
p=p->pre;
}
}
}
其他方法
这题我是手写的链表,主要原因是对list容器不熟TAT,不知道连接list时该如何操作,以及该如何存储每个盒子中的链表。在讨论区还看到大佬们说可以采用图来解决,我是小白,对图所知甚少,欢迎大佬们赐教!