ZTE 2020 傅里叶祭品传递赛题
语言: c++
主要思路:DFS搜索路径、剪枝、set去重
一阶段耗时10s,二阶段耗时 300 s左右。
菜鸡历程:
第一阶段 数据大小 256*640
-
刚看这题,不难啊。建立邻接表 采用深度优先搜索遍历一遍不就好了。由于不计重合数,那就以A部落的人为搜索起点。
-
目标能出结果就行。好的,四十分钟能跑到12环。gg~ 那怎么办呢?同一个环被计算多次,那每个环仅仅以最小节点为起点开始统计。这部分改代码就很轻松,只要在DFS里面加个条件,下一步搜索的节点不能小于起点start。二十多分钟能跑完。 (此处作者用的数据有个小坑,下一步再讲)
-
想一想DFS一般的加速技巧,剪枝思路。事先计算最后一步能否直接回到起点,这样少搜索一层,不就会快很多。 那可以事先计算两步,三步,四步之内能回到起点的节点。 怎样计算这个节点能否到达,作者就以邻接矩阵的几次幂来代表几步内能到达。matlab直接用稀疏矩阵的乘法很快就能算出来。那我们用c++,可以调用Eigen库来计算。 就先实现功能,不考虑效果/dog。 实际效果也很快。一分钟就能搜到结果。只计算能否回到起点,并不需要矩阵的幂计算操作,那把矩阵计算的代码优化,能在十几秒出结果。
-
此处讲讲作者考虑不周的地方,前两次的下载的数据中就用上面的方法就得满分了。作者没考虑到一个环中的节点连接顺序不一样会被重复计算几次, 然而作者下载的两次数据中并没有重复的环从而忽略了这个错误。 第三提交就是99点几分。 那写去重算法了。
-
首先想到的就是存所有路径再去重,具体怎么写,还是先百度,了解到去重用hashmap 速度很快。没有默认给数组计算hash的函数,作者也真是个弱鸡,不知道该怎么办。 后来想到一个虽然性能很差当可替代的方案。可以把数组排序然后合并为一个字符串存在hashmap里面其中key和值都是字符串, 完美解决。刚开始是搜索map里面有没有存入某个环的string,没有就把当前环个数加一,再把该环的string加入。后经提醒,直接插入,最后读取hashmap中元素的数量,耗时三四分钟。
第二阶段 数据大小 1344*1344
-
题目出来一天后跑了下,九十分钟能出结果。 打开vs的性能探查 耗时大户: 排序算法、数组转为字符串并合并、存hashmap和清空hashmap。 优化方向 把int型数组转字符串那个消灭,看了在自定义结构的hash作者还没学会。那改用set直接存int型数据不就好了。set采用平衡二叉树也不算太慢。 此步优化后效率有着显著的提升,二十多分钟。好歹能提交个有效成绩。 同时这个数据长度很短,没有用sort的排序方法,自己写个插入排序的方法。最后和sort相比,没什么差别,才了解到sort函数是智能判断数据有多少,采用快排还是桶排序插入排序,同时它实现的快排还比作者写的快排快。 早知道就不改了。
-
想着继续优化下去,看了有人说tanner图,去看了两篇文献,真是打扰了,理解了其中的一个思路,计算所有的可达路径然后减去某些提前到达或者小环的数目,放到这道题中思考下,作者还是不会去掉同一组节点能构成重复的环,遂放弃。set换成unordered_set,这个是基于hash存储的,查找效率要高一些,重写其hash函数。(作者学会了自定义结构的unordered_set的,也是这个比赛的收获之一吧)
-
想到节点的数量为1344,再路径中第一个节点的值向左移12位,再加上后一个节点。一个int可以存两个节点, 按照这个思路一个longlong 可以存五个节点,这样省空间多了。同时set集合中加入发生hash碰撞之后,进行值的比较,比较次数也较少。现在想来这步几乎是多余的,可能会更浪费时间。
-
群里大佬太多,作者也没啥优化思路,遂放弃。 最后也优化下代码,向量用数组替换,环的第一个节点不需要存储,提交完事。最终花费时间在300s左右,看cpu和数据csv的数据了。 第一阶段花费在十秒左右
大佬几秒十几秒。加一个零都比我快。没啥优化的动力。同时当时也没思路,即使在代码细节上的修改能带来性能的部分提升,重要的是还要回到算法和数据结构本身来。
另外有个思路,不知道可行不可行,作者没有实验,就是采用dfs剪枝,优化后,对搜到的每条路径中的节点 再次搜索该组节点, 判断是否能构成其他不同顺序的环,能构成就记录下此节点组以及能构成多少不同的环(下次搜索到同样的节点的不同环就可以避免搜索,不能构成就不记录,由于搜索数最大为14个节点,这一步应该能很快搜索把。同时内存占用应该能大大减小) 但不知道具体的速度怎样。有没有胖友这样做的,能告知大概多少时间吗?赛题结束了,不想试验了。
最后,完成一件事记得多总结,分享,说不定就有新思路了。 现在想来里面还是存在好多可以再次优化细节。 splin的菜鸡笔记。
github代码: github代码
题目
在某片遥远的大陆上,居住着两个世代友好的部落,分别是部落A和部落B。他们一起耕耘劳作,互相帮助,亲如一家。久而久之,部落里的每个人都在对方部落里找到了志趣相投,互相欣赏的好朋友。有的人性格热情开朗,好朋友很多;有的人性格沉稳内敛,好朋友相对少一些。
每到秋天丰收的季节,这两个部落的人民都会聚集在一起举行盛大的“丰收祭”,来祈祷下一年的风调雨顺。今年的丰收祭马上又要举行了。为了进一步增进两个部落的友谊,也为了明年能有一个好收成,这两个部落的祭司们商量后决定:在今年的丰收祭前举办一场特别的“击鼓传花”游戏。只不过游戏中并非有人真的击鼓,并且所传递的“花”也不是真的花,而是等待在丰收祭上献上的祭品。
游戏规则如下:
-
两个部落的所有人都可以事先准备自己的祭品,且每个人的祭品样式都不同,每一个祭品都分别盛放在一个相对应的木托盘里;准备此祭品的人熟悉自己的祭品;
-
每个人可以准备的祭品数量不限;祭品的最小不可分割单位是1份;
-
游戏开始后,在整个游戏过程中,每个人都能且只能将祭品(包括木托盘)传递给自己在对方部落里的好朋友们,每个好友可以接收的祭品数量不限;
-
收到祭品的人必须在盛放此祭品的木托盘上刻上自己的名字(代表留下自己美好的祝愿),随后按照上一条规则,继续传递;
-
如果祭品回到最初准备此祭品的人手中,此人也在木托盘上刻上自己的名字之后,终止传递;
-
木托盘上不允许出现重复的人名,如果无法满足此条件,则不再继续传递该祭品;
-
当所有的祭品都不再传递后,游戏结束;
游戏开展得非常顺利。游戏结束后,祭司们将收集同时满足如下三个条件的祭品用于接下来的丰收祭活动:
-
此祭品回到了最初准备它的人手中;
-
盛放此祭品的木托盘上至少有4个名字,至多有14个名字;
-
如果有多个木托盘上的名字完全一样(不区分名字的排列顺序),则从其中随机选择一个木托盘所对应的祭品。
问题:
已知这两个部落里的所有人都不重名,并且部落A的人和部落B的人之间的好朋友关系以附件的csv数据表格文件给出,其中行索引代表部落A中的人,列索引代表部落B中的人,表格中的数字“1”代表他们两人是好朋友,“0”代表他们两人不是好朋友。请问:
如果以木托盘上的名字的数量对用于丰收祭的祭品分类,每一类分别最多有多少个祭品?
请参赛者答题:
木托盘上有4个名字的祭品最多有()个;木托盘上有6个名字的祭品最多有()个;木托盘上有8个名字的祭品最多有()个;木托盘上有10个名字的祭品最多有()个;木托盘上有12个名字的祭品最多有()个;木托盘上有14个名字的祭品最多有()个;
请在每个()中填写一个正整数答案;