【BIT2021程设】20.军训日记:查寝

写在前面:

本系列博客仅作为本人十一假期过于无聊的产物,对小学期的程序设计作业进行一个总结式的回顾,如果将来有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里

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秒64M

题意分析:

        都做到这了,肯定直觉就告诉你模拟不行了,试都不用试。

        来吧,想想有没有更快的办法。

        我们试想一下,搬动这个操作本身的意义是什么:假如我的物品序列自上而下是ABCDEFG,我要把他们搬到一个新箱子里去,新的序列就是GFEDCBA,如果要再搬一次,又会变成ABCDEFG,这是啥的性质呀(别跟我说这是栈,我会揍你,说了不让你模拟了),很明显这应该是一个链式存储的性质,因为如果某个元素已经有了前驱和后继,那么这个前驱和后继在搬动过程中是不会发生变化的,如果没有前驱和后继,在搬动过程中也只会唯一地更新一次前驱或后继,这也符合链式存储的连接方法。然而常规的单链表是行不通的,因为我们不仅需要从A到G的序列,还需要从G到A的序列——解决方法也很简单,使用一个双链表就可以了。

        事实上,确实有大佬是双链表过的,直接用的STL就行,然鹅我当时在做这题的时候并不会用STL的逆序迭代器,导致了每次都要重新做一次reverse操作——我以为这是不耗时的,只是改变一下首尾指针和方向的问题,没想到写STL库的人的想法和我的想法不是很一致。所以!我在百般无奈之下,偷偷学了别的大佬的数组解法,我在这里详细讲一下吧,我觉得挺巧妙的,至于STL的双链表解法(或者自己做出来的双链表)思路比较简单,我就不细说了。

        这个数组解法的核心和双链表法很像,本质就是找前驱和后继,然而他做的更绝一点——甚至没有一个线性的存储结构,单纯只是记录每一个节点的相邻节点(不区分前驱和后继,因为倒来倒去的过程中会乱的),这样我们就可以利用一个n\times2的数组完成整个存储。举个例子:假如我有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;  
    }  

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千里之码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值