输入为包含资金流水的文本文件,每一行代表一次资金交易记录,包含本端账号ID, 对端账号ID, 转账金额,用逗号隔开。
- 本端账号ID和对端账号ID为一个32位的无符号整数
- 转账金额为一个32位的正整数
- 转账记录最多为200万条
- 账号 A给账号B最多转账一次 举例如下,其中第一行[1,2,100]表示ID为1的账户给ID为2的账户转账100元:
1,2,100
1,3,100
2,4,90
3,4,19
4,1,95
2,5,95
5,4,90
4,6,30
6,7,29
7,4,28
输出信息为一个文件,包含如下信息:
- 第一行输出:满足限制条件下的循环转账个数 说明:数据集经过处理,会保证满足条件的循环转账个数小于2000万。
- 第二行开始:输出所有满足限制条件的循环转账路径详情。输出循环转账路径要按照指定排序策略进行排序:每条循环转账中,ID(ID转为无符号整数后)最小的第一个输出;总体按照循环转账路径长度升序排序;同一级别的路径长度下循环转账账号ID序列,按照字典序(ID转为无符号整数后)升序排序。 举例如下:
3
1,2,4
4,6,7
1,2,5,4
- 循环转账的路径长度最小为3(包含3)最大为7(包含7),例如:账户A给账户B转账,账户B给账户A转账。循环转账的路径长度为2,不满足循环转账条件。
- 转账金额浮动区间约束为[0.2, 3] :循环转账的前后路径的转账金额浮动,不能小于0.2,不能大于3。如账户A转给账户B 转X元,账户B转给账户C转Y元, 那么进行循环检测时,要满足0.2 <= Y/X <= 3的条件。
该题本质上是有向图的找环算法,其中初赛是限制条件环的长度为[3, 7],复赛限制为环长[3, 7]和相邻边权重比限制为[0.2, 3]。
数据结构,我们采用的是 vector<int, vector>结构。 (根据参加比赛的其他队伍讲,使用数组尤其是静态数组速度要快很多)
数据读入有标准的cin, fread, mmap三种,对大量数据的读取, mmap速度最快,我们使用的是fread,(因为开发的时候在windows系统上, mmap用不了)。
关于输出格式的问题,可以从小到大找以各个结点为头的环,比如2->1->3->2, 我们找到2结点时候,发现下一个结点小于头结点,直接跳出,寻找下个结点为头结点的环,这样我们找到的就是1->3->2->1, 可以省去最后调整的时间。对于不同长度的环分开储存,最后总共五个数组,输出时候依次输出。
有向图找环用的比较多的是DFS。不同之处在于找环时候的思路,比较直观的是单向单层的DFS,直接找7层,这样是2^7,太慢了。我们的思路是先通过BFS先标记到每个结点距离为3的结点,在这些结点中找剩余的结点。这样最多只会找3层,速度又很大提升。大神的队伍都是使用多线程,进行双端向内搜索。
还有一个思路,对稀疏图来说,先使用拓扑排序把出度或入度为0的结点给筛掉,因为只要是构成环路,必定既有出度又有入度。
本次比赛对我来说收获颇丰,通过题目的编写,思考,及反馈。首先是对基本的c++语法更加熟悉,对各种结构的组合有了进一步的了解,如vector<int, vector> 感受到了语言的灵活性, 像是打开一扇大门。 其次,由于比赛是按时间排名,也对平时不太注意到的细节有了认知,比如循环中不要写太多if语句,数组比vector要快, 静态数组要比动态数组快,之前只是知道这些概念,并不知道为什么会有这么多类似的结构。还有一些细节的问题,double数据由于精度问题不能直接比较大小。在复活赛中,有一个调整是把转账金额换成不超过两位的浮点数,由于条件二的限制,需要比较浮点数,有人就想到了将其乘100转换成整数,属实精妙。当然,还有一些看到,想到并没有完全消化的概念,如Cache Miss, 如多线程,没来的及详细了解,之后还要继续学习。