查寝 | c++ | 不用双向链表(上篇)

一、题目描述

小军的军训进行到了一半了,今天军训教官搞了一波突然袭击,进行了一个寝的查。

提前了解到查寝消息的小军准备进行一波整理归纳,来使自己的寝室变得更加整洁。具体来说,小军有 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里

1n≤10^5, 1≤m≤10^6, 1≤x,y≤n

题目保证x != y

输出:

有 n 行输出

第 i 行,先输出一个正整数 k ,表示第 i 个盒子内的物品数,接下来输出 n 个数,表示第 i 个盒子自顶向下的物品标号

注意:

行末无空格,文末有回车。

测试输入期待的输出时间限制内存限制额外进程
测试用例 1以文本方式显示
  1. 3 2↵
  2. 1 2↵
  3. 2 3↵
以文本方式显示
  1. 0↵
  2. 0↵
  3. 3 2 1 3↵
1秒64M0

 二、思路分析

        这个真的是我的思路,每次写的都是我的心路过程,至于有时候跨度确实大,能不能描述清楚就另说。

思路一:

        最开始的思路是最简单、不加掩饰、同时也是最耗时的原始方法。考虑到每一个物体的取放十分符合栈“先进后出,后进先出”的规则,我便建立 n 个结构体box[n],每个box都是一个栈,每次读入一组数据x y,表示将第x个盒子里的东西拿到第y个盒子里,每次将box[x]的栈顶元素取出放在box[y]的栈顶,然后box[x].pop......如此循环。

        这种代码的优势是美观整洁,十分容易理解,然而缺点十分致命:它会超时

        考虑最坏情况,我有1e5个箱子,第一次操作我将第一个箱子里的东西放入第二个箱子,第二次操作我将第二个箱子里的东西放入第三个箱子,......,第 i 次操作我将第 i 个箱子里的东西放入第 i+1 个箱子,......,依次操作n-1次。不难发现,第一个箱子里的第一个物品,就这样被反反复复地拿了n-1次,我们的程序也这样傻乎乎的进行了n-1次,十分的耗力耗时。

        上面的情况还可以更坏,经过上面的操作之后所有的物品都装到了最后一个箱子,接下来的 k 次操作中,我把它们从最后一个箱子里拿到第一个箱子里,然后把它们从第一个箱子里拿到最后一个箱子里,反复操作。明明只是把顺序反过来,在我们的认知里颠倒一次,只要操作一次完全足够。但是程序就会反反复复操作 n 次,进行一个时的超。

思路二:

        看来简便的代码就注定会有亿点点的超时,那就只好优化算法。

        双向链表

        不难知道,当一个物品与另一个物品接触后,它们之间就形成了一种“连接关系”。假设原来的顺序是1 2 3 5 6,那它经过一次操作只能变为6 5 3 2 1,不会横生枝节,也不会打乱顺序。和大一时接触的链表不同,单向的链表不能满足这道题目的基本需求,那么于是就需要恰当的引入双向链表的相关内容。

        说实话,在这以前,我连这玩意都没听说过,甚至是链表基础在大一就学的稀巴烂。今天我知道了这个概念,但我不打算用它。换言之,既然我不会双向链表,我尝试使用我学过的其他结构体去模仿这玩意儿,达到和双向链表几乎一样的效果。

(如果你会双向链表的话,当然做起这道题来会更简单啊)


三、思路三 · 半成品

我想使用二维数组+结构体的形式来“逼近”双向链表。

考虑构建这样的一个结构体:

struct artical 
{
	int id;
    int next ; 
    int previous ;
};
artical a[N];
//id是这个物品的序号,next是下一个物品的序号,previous是上一个物品的序号
//初始时next和previous都为0,表示没有与之连接的物品

不难看出,next和previous充当链表中指针的作用。

(后期补充,当时定义了id这个东西,但是它其实没有用,你可以看到我的最终代码里已经把它去掉了。不过作为我的思维过程的一部分,我决定保留。)

初始化箱子:

int box[n+1][3];
for (int i=1;i<=n;i++) {
	a[i].id = i;   
	a[i].next = 0;        // 等于0表示不指向其他物品
	a[i].previous = 0;    // 等于0表示不指向其他物品
	box[i][0] = 1;
	box[i][1] = i;        //1为顶 
	box[i][2] = i;        //2为底 	
}

        其中的二维数组box[x][y],x表示第x个箱子;y只能为0、1、2。当y=0时,其值是物品数量,初始为1;当y=1时,其值是箱子顶部的物品,初始为x;当y=2时,其值是箱子底部的物品,初始为x。

       

和以往不同,这次我先写的是输出,再写其他操作。

//输出 
for ( int i=1 ; i<=n ; i++) {
    //输出物品数量
	printf( "%d" , box[i][0] );

    //如果有物品,就输出物品id。
	if ( box[i][0] ) {
		
        //如果物品只有一件,输出这个物品id就行了。
		if ( box[i][0]==1 ) printf( " %d" , a[ box[i][1] ].id );

        //否则物品不止一件,要先确定它是头(next)还是尾(previous),然后输出。
		else {
			int j = box[i][1];
			if ( a[j].next ) do { printf( " %d" , a[j].id );
									j = a[j].next; } while (a[j].next);
			else 		     do { printf( " %d" , a[j].id );
									j = a[j].previous; } while (a[j].previous); 	
		}
	}	
	printf("\n");
} 

(一个小时过去了)

使用next和previous有一项极大的不方便,举个例子更好说明:

假设我现在要将物品2放在物品1上面:

其中,2.next -> 1,1.previous -> 2。

再把3放在2的上面,于是3.next -> 2,2.previous -> 3.

同样有另一堆物品,由4和5组成,且4在5的上面,4.next -> 5,5.previous -> 4

 我现在要把321这一堆叠在45这一堆上面,变成12345,在3和4的连接处,应当有3.next -> 4

这把上面的3.next -> 2覆盖掉了。

        要解决这个问题,就要对3.next -> 4,2.next -> 3,1.next -> 2全部重新设置一遍,当数据量大的时候仍然会出现操作重复、浪费时间的情况。

不过即便是会超时,我们写出了一个半成品,半成品代码如下。

#include<bits/stdc++.h> 
using namespace std;
const int N = 1e5 + 10;
struct artical 
{
	int next=0 , previous=0;
};
artical a[N];

int main(){

	int n,m;
	scanf( "%d %d" , &n , &m );
	
	int box[n+1][3];
	for ( int i=1 ; i<=n ; i++) {
		box[i][0] = 1;
		box[i][1] = i; //1为顶 
		box[i][2] = i; //2为底 
	}
	
	for ( int i=0 ; i<m ; i++ ) {
		
        //把x的物品放到y
		int x,y;
		scanf( "%d %d" , &x , &y );
		if ( box[x][0] ) {
			for ( int j=0 ; j<box[x][0] ; j++ ) {

                //每次取x顶的物品,对其next和previous进行重置	
				a[ box[x][1] ].previous = a[ box[x][1] ].next;
				a[ box[x][1] ].next = box[y][1];
				
                //更新y顶的x顶的物品				
				box[y][1] = box[x][1];
				box[x][1] = a[ box[x][1] ].previous;
			}
			
            //更新x和y中的物品数量
			box[y][0] = box[x][0] + box[y][0];
			box[x][0] = 0;
            //更新x底的物品
			box[x][2] = 0;
		}
		
	}
	
	//输出 
	for ( int i=1 ; i<=n ; i++ ) {
		printf( "%d" , box[i][0] );
		if ( box[i][0] ) {
			
			if ( box[i][0]==1 ) printf( " %d" , box[i][1] );
			else {
				int j = box[i][1] ;
				printf(" %d",j);
				while(a[j].next) { j = a[j].next; printf(" %d",j); } 	
			}
		}	
		printf("\n");
	} 
	
	
	
	return 0;
}

有一个样例超时。

休息一会儿,等今晚或者明天再优化,届时在《下篇》中有思路四(思路三的优化版本)的呈现。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值