写在前面:
本系列博客仅作为本人十一假期过于无聊的产物,对小学期的程序设计作业进行一个总结式的回顾,如果将来有BIT的学弟学妹们在百度搜思路时翻到了这一条博客,也希望它能对你产生一点帮助(当然,依经验来看,每年的题目也会有些许的不同,所以不能保证每一题都覆盖到,还请见谅)。
不过本人由于学艺不精,代码定有许多不足之处,欢迎各位一同来探讨。
同时请未来浏览这条博客的学弟学妹们注意,对于我给出完整代码的这些题,仅作帮助大家理解思路所用(当然,因为懒,所以大部分题我都只给一个伪代码)。Anyway,请勿直接复制黏贴代码,小学期的作业也是要查重的,一旦被查到代码重复会严厉扣分,最好的方法是浏览一遍代码并且掌握相关的要领后自己手打一遍,同时也要做好总结和回顾的工作,这样才能高效地提升自己的代码水平。
加油!
成绩 | 0 | 开启时间 | 2021年09月3日 星期五 11:00 |
折扣 | 0.8 | 折扣时间 | 2021年09月14日 星期二 23:59 |
允许迟交 | 否 | 关闭时间 | 2021年10月10日 星期日 23:59 |
小军的军训进行到了一半了,今天军训教官搞了一波突然袭击,进行了一个寝的查。
提前了解到查寝消息的小军准备进行一波整理归纳,来使自己的寝室变得更加整洁。具体来说,小军有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个盒子自顶向下的物品标号
注意:
行末无空格,文末有回车。
测试用例 1 | 以文本方式显示
| 以文本方式显示
| 1秒 | 64M |
题意分析:
都做到这了,肯定直觉就告诉你模拟不行了,试都不用试。
来吧,想想有没有更快的办法。
我们试想一下,搬动这个操作本身的意义是什么:假如我的物品序列自上而下是ABCDEFG,我要把他们搬到一个新箱子里去,新的序列就是GFEDCBA,如果要再搬一次,又会变成ABCDEFG,这是啥的性质呀(别跟我说这是栈,我会揍你,说了不让你模拟了),很明显这应该是一个链式存储的性质,因为如果某个元素已经有了前驱和后继,那么这个前驱和后继在搬动过程中是不会发生变化的,如果没有前驱和后继,在搬动过程中也只会唯一地更新一次前驱或后继,这也符合链式存储的连接方法。然而常规的单链表是行不通的,因为我们不仅需要从A到G的序列,还需要从G到A的序列——解决方法也很简单,使用一个双链表就可以了。
事实上,确实有大佬是双链表过的,直接用的STL就行,然鹅我当时在做这题的时候并不会用STL的逆序迭代器,导致了每次都要重新做一次reverse操作——我以为这是不耗时的,只是改变一下首尾指针和方向的问题,没想到写STL库的人的想法和我的想法不是很一致。所以!我在百般无奈之下,偷偷学了别的大佬的数组解法,我在这里详细讲一下吧,我觉得挺巧妙的,至于STL的双链表解法(或者自己做出来的双链表)思路比较简单,我就不细说了。
这个数组解法的核心和双链表法很像,本质就是找前驱和后继,然而他做的更绝一点——甚至没有一个线性的存储结构,单纯只是记录每一个节点的相邻节点(不区分前驱和后继,因为倒来倒去的过程中会乱的),这样我们就可以利用一个n2的数组完成整个存储。举个例子:假如我有100个数据,我就开一个int next[100][2]的数组,其中next[i]存储了两个和i相邻的数字,表示相互之间的关系。
然而,这有什么用呢?我一开始也不是很理解,直到自己慢慢思考了一下大佬的做法,顿感其中妙处。
首先,单纯记录相邻节点是不够的,因为我们需要自上而下地输出最终每个盒子的内容,所以我们仍然需要记录这个方向。这里我们可以设置一个Box结构体,记录一个top和一个bot,这样就可以知道如何输出了,只要从top对应的数字一路往下搜,next[top]中的两个位置必然只有一个有元素,记为cur,那么next[cur]中的两个位置必然有一个是top,那么我们就需要另一个用来更新cur......如此循环往复,直到找到bot,我们的任务就完成。这个整体思路有点像图的邻接表表示法(当然,当时还没有学习到图,所以觉得妙不可言)。
那么现在我们就只需要考虑每一次任务请求应该如何处理——其实很简单,大体的逻辑就是,如果我们要把from盒里的东西搬到to盒里,那么to盒的to.top对应的next[to.top]需要更新一个from.top,相对的,next[from.top]也要更新一个to.top,同时to盒的top需要更新为from.bot,同时把from盒里所有内容清零,这个逻辑应该是非常自然的。
不过还有一些细枝末节的边界条件需要处理,就是把东西从零盒搬到某盒,或者从某盒搬到零盒,或者零盒搬到零盒应该怎么处理,这个大家自己思考思考应该很快也能想出来,我就不废话了。
至于输出嘛,应该前面也讲得很详细了,唯一要注意的就是,由于我们的next数组没有区分也不能区分前驱和后继,我们在输出的时候需要记录一下哪些数据已经输出过了,这个简单地用一个bool数组就可以解决。如果不这么做的话,举个例子,比如1的相邻节点是2,3,而2的相邻节点是1,5,我们输出了1,之后输出2,然儿1又在2的相邻节点里面,这就陷入死循环了,所以需要这么一个存储已经输出过的数据的结构。在这里,由于每个数据会且仅会被输出一次,所以我们只需要开一个bool数组就行,不需要每次都重新打个表。
伪代码:
构建一个结构体Box,存储top,bot,count三个数据,其中count可要可不要;
遍历m个任务请求,每次任务更新Box[from]和Box[to],具体更新方法写在上面了,记得处理一下边界情况;
输出的时候,检查每个盒子的count是否为0,如果是0直接输出0跳过,
如果不是0,先输出count,然后从top开始,一路沿着next数组向下搜,直到搜到bot为止;
贴代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f;
const double EPS = 1e-8;
#define __MAX 100010
struct Box{ //结构体,保存盒子的相关属性
int top = 0;
int bot = 0;
int count = 0;
} b[100010];
int nxt[100010][2]; //保存每个节点的相邻节点
bool isOutput[100010]; //记录是否输出
int main(){
///ifstream infile("input.txt", ios::in);
///ofstream outfile("output.txt", ios::out);
int n, m;
cin >> n >> m;
memset(isOutput, 0, sizeof(isOutput));
memset(nxt, 0, sizeof(nxt));
for(int i = 1; i <= n; i++){ //初始化,每个盒子i都只有i元素自己
b[i].top = i;
b[i].bot = i;
b[i].count = 1;
}
int from, to;
int t1, t2;
bool flag1, flag2;
for(int i = 1; i <= m; i++){
scanf("%d %d",&from, &to);
if(b[from].count == 0) //from为零盒
continue;
else if(b[to].count == 0){ //to为零盒
b[to].top = b[from].bot;
b[to].bot = b[from].top;
b[to].count = b[from].count;
b[from].count = 0;
b[from].top = 0;
b[from].bot = 0;
} else{ //一般情况
for(int j = 0; j < 2; j++){ //找到from.top相邻位置中空的那一个
if(nxt[b[from].top][j] == 0){
nxt[b[from].top][j] = b[to].top;
break;
}
}
for(int j = 0; j < 2; j++){ //类似,找到to.top相邻位置中空的那一个
if(nxt[b[to].top][j] == 0){
nxt[b[to].top][j] = b[from].top;
break;
}
}
b[to].top = b[from].bot; //更新from盒和to盒
b[to].count += b[from].count;
b[from].count = 0;
b[from].top = 0;
b[from].bot = 0;
}
}
for(int i = 1; i <= n; i++){ //输出
if(b[i].count == 0){ //零盒,直接输出
printf("0\n");
continue;
} else{
printf("%d",b[i].count);
int temp = b[i].top; //从top开始向下遍历
while (true){
bool stop = false;
if(not isOutput[temp]){
printf(" %d",temp);
isOutput[temp] = true;
}
t1 = nxt[temp][0];
t2 = nxt[temp][1];
flag1 = false;
flag2 = false;
if(t1 == 0 or isOutput[t1])
flag1 = true;
if(t2 == 0 or isOutput[t2])
flag2 = true;
if(flag1 and flag2)
break;
else{
if(flag1)
temp = t2;
else
temp = t1;
}
}
printf("\n");
}
}
return 0;
}