大家好,我是月光。
历时一个多月的软挑终于落下了帷幕,收获了一个还不错的名次。这个不错是对我们自己而言的,可能对于观众来说,记住的永远只会是第一名,而其他角色终会随比赛过去而被遗忘。其实本身我希望过的平静一些,甚至连这篇分享都没想要写出来,毕竟自己也没做什么大不了的事(不是懒,不是懒,不是懒,重要的话说三遍)。奈何各位大佬抬爱,不断追问我们决赛的方法,为免一一回答,我就简单的写写我们队的做法吧。
一、赛题分析
(决赛大佬们直接略过,去看后面好了)
官 赛题,不知道什么时候过期:
http://codecraft.huawei.com/home/detail
复赛时大佬们还在拼命地优化费用流算法,增加迭代次数。结果决赛赛题突变,使得之前的优化算法和费用流都显得无足轻重。由于决赛赛程特别短,能在短时间内分析出赛题的本质就显得非常重要了。
为此,武长君还特意策划了一次赛区内部交流,在华为专家的帮助下,总结出了很多有用的信息。
1. 优化目标从复赛的两部分:服务器成本+链路成本,变成了三部分:服务器成本+链路成本-消费节点回 。
2. 赛题从复赛的单次结果输出,改为了60轮输出,说明一次结果不重要,每轮平均收益才能决定胜负。
3. 单轮时限改为10s,时间缩短了近10倍。复赛大家已经把费用流优化地出神入化,决赛还想提高个一倍已经是不太现实了,现在时间缩短10倍,又增加其它要优化的目标,沿用之前的智能优化算法已经是基本无望,考虑改进或者放弃优化算法。
4. 二人对弈,对于每个消费节点,只会给予提供流量最多的玩家回 。所以要么给足流量,要么不给流量,给予期待值以下的流量只会造成浪费。
5. 从官方给的用例来看,链路成本相较服务器成本来说较低,相较消费节点回 也较低。
还有一些次要信息,不一一列出了。
二、思路框架
首先,三部分的优化目标混合在一起,很难同时求解。在这里,可以做一个划分,由于服务器成本(包括位置和档次成本)是一次性缴纳的,变动代价很高,而链路成本每回合都要缴纳,变动代价低,考虑分开求解。实际上,服务器的位置与消费节点相关性不大,因为每轮要服务的消费节点是不同的,布置在任何位置的服务器在平均水平上讲都差不多。之后可以将链路成本与每轮要满足的消费节点回 合并求解。
在很早的时候,我们就定下了大致思路:
1.先使用某种排序方法确定购买的服务器位置和档次。
2.之后使用优化方法求解要满足的消费节点。
3.在这之中,利用对手信息改变策略。
三、关于初赛复赛的联想
(这节略有些跑题,大致看一下好了)
初赛复赛是求解服务器成本与链路费用最小,大家的做法大致都是底层:费用流+上层:智能优化(或搜索)。
费用流的做法有很多很多,不过能进到复赛四强的主要也就只有三种,zkw、primal-dual、network simplex。 络单纯形最快,但难以理解,实现复杂,使用不灵活。前两种实现比较简单,zkw适用于稠密图,原始对偶适用于稀疏图,在这次的题目中,裸的算法zkw要比pd快一些,但是pd有着很灵活的使用方法,可以实现单点增量费用流,我个人认为有超过 单的潜力。但具体做法比较复杂,略去不谈。
实际上在决赛中,费用流再快也撑不起智能优化算法, 一般也就只能用来求一下最终解,所以这三种算法基本都是可以用的。
上层算法其实更加杂乱,据我所了解的就有模拟退火、遗传算法、线性规划、分支限界等等一系列有名字的算法,还有数不胜数的xjbs算法,这些算法拼到最后靠的都是迭代次数和启发式。
不过有一种没有名字的算法我必须要在这里提一下。这种算法大致做法是在初始的时候选中了所有的服务器,然后进行迭代费用流,并且每次修改超级源到服务器的边为服务器成本除以上次迭代时该服务器的流量,直到迭代的结果不再改变。
整个赛程中,我听到至少5支队伍跟我讲过这种做法,当然实际使用中还有别的修改。至于这个算法为什么会取得效果,我也是想了很久才只懂了一点点,有大佬懂的请留言给我讲讲为什么。这次决赛做法的灵感就取自这里。
整个初赛复赛比我们的决赛要精彩多了,然而一个复赛赛区第四名队伍写什么总结……(不是懒,不是懒……)
四、决赛思路详解
终于要到正题了,讲之前先交代一下时间。虽然思路框架很早就定了,但是直到5月8日我还在思考怎么把pd和退火用到这次比赛里。直到5月8日夜里,我开始着手删没用的代码,才放弃之前的挣扎,想出了这个做法,并且用了两天到5月10日晚就完成了,所以不要对这个算法难度有太高的期待,简单实用。
很多人认为这个题目的状态很复杂,要设计很精致的策略才能够取胜,但是我不这么认为。化繁为简,是我一贯尊崇的美学,优美地解决问题才能不被琐事打扰。
另外西北的土豆大佬也想到了这个做法,并且我们互相打了两天才发现写的一样,他们队代码加上了 络单纯形才区区800行,只比我多了200行。
在决赛中,一旦决定买服务器,那么就要早买,因为提供的流量可以获得巨大回 ,相反卖服务器所得少得可怜。故而有钱就买服务器,直到某回合再也不买。下面按照思路框架来讲细节:
1. 选择服务器
服务器的选择要考虑部署成本与输出能力,有人会觉得链路成本也应该考虑进去,我这里直接给出答案,是不需要的,因为链路成本相较于单点回 来说,过于低,以至于只要有输出能力,那么就能赚的比对手多,因为消费节点只会选最多流量的玩家。因为每轮所服务的消费节点在变化(要考虑对手信息),所以服务器的购入不需要考虑某一些特定的消费节点。
我的选择服务器方案是对于每个点,选出性价比最高的档次,性价比按照如下定义:
然后将每个点按照最高档次性价比从高到低排序,依次选择购入。
购入时存在一个问题,如果该点的地形输出能力不够,导致实际的流量达不到档次流量,此时档次要退化。另外相邻的服务器也有可能会互相影响,导致输出能力下降。本次比赛中我采取的策略是,跑一次费用流,计算目前的所有服务器是否满流,如果满流,则购入,否则舍弃该服务器。由于采用了费用流,耗时会较长,所以每轮只花当前有的钱,就不会超时。
后来水能载舟exciting的大佬说可以用最大流代替费用流,我只能说,我为啥没想到呢/span>
其实能改进的地方还有很多,我只是告诉大家,在这次决赛中,流量才是最重要的,用最少的部署成本买到最大的流量,才能实现滚雪球,从而压得对手不能翻身。
2. 选择消费节点
买入大流量服务器后,考虑这样的问题:假如没有对手,对于所有消费节点,我都只给最低需求,那么我应该选择哪些消费节点能获得最大回 呢/span>
上一节中提到了的初赛做法,是求服务器成本与链路成本最低,重新表述为:
初始化:
设超级源S与超级汇T,将每个节点都设为服务器,连超级源至每个服务器的边,费用为0,流量无限大。连所有消费节点到超级汇的边,费用为0,流量为消费节点需求。
迭代:
1) 求费用流。
2) 对比每个服务器当前与上一轮的流量,如完全相同,结束迭代,否则记录当前轮的流量。
3) 修正超级源到服务器的边。枚举每个服务器,如果这个服务器有流量,则将超级源到其的边修正为服务器成本除以其流量;如果没有流量,删除该服务器。
结束:
当前流即为解。
由于将服务器成本融入了链路中,所求最小费用流就是最终的优化目标。这个方法可以保证在几次迭代后就获得一个局部最优解,具体证明确实没有搞懂,只能说有效果,在初赛中结合一些重新引入被删除节点的方法,可以打进复赛。
在决赛中,固定服务器后,把消费节点作为状态,优化目标变为链路成本减去消费节点的回 最低,将消费节点的回 的负值均摊到消费节点到超级汇的链路上,初赛的方法可以完美适用。
这样求出来的消费节点,在链路成本足够低的情形下,就是需求最低的消费节点之和。所以在初期,完全可以只抢占需求低的点,来达到同样的目的。当随着对局时间增加,单点的链路成本增加后,这个方法可以求出较为合理的解。
3. 策略
刚才讨论完了给定消费节点的目标需求,如何求解最大回 的方法,最后一步就剩下每轮计算所有消费节点的目标需求了,在此我由于没有时间考虑,直接给了一个非常简单的策略:
上一轮被对方占领的点的目标需求+5,其余不变。
相信木桶原理大家都听过,在这个题目中,如果我们给的流量参差不齐,那么也容易被对手利用到,所以我在初始化时进行了处理,将所有小于一个定值的点都修改成了这个定值,之前的算法就能够实现大水漫灌的思想,将同一水平线的点全部覆盖,从而使平均收益最高。而之后使用+5策略可以使得要满足的消费节点需求稳步上升,保持平均,敌方不占领的节点的需求不变,可以利用对手失误。
赚到钱后,将所有资金全部用来购入服务器。
事实上,由于上一段的方法过于强势,所以无需任何多余策略就能战胜大部分的队伍,消费节点数总是压倒性优势。而数倍于对手的占领点,使得我官 测试用例中 一回合就能收入四五十万,后期完全不愁。
至于买卖服务器的回合调整,不是很重要,就都融到下一节中讲了。
五、比赛过程
(废话中穿插思路,简单看看就好)
8 才开始动手写代码,完全错过了两次模拟赛,写到10 中午才成型了第一个版本,但是当时还不能完全摆脱恋旧情节,想要把拆边的旧方法融进去,让费用流能自动控制目标需求。这是完全不现实的,那个版本也是谁都打不过,索性一删到底,用+5的方法控制目标需求,到晚上八点半才基本写好。
这时我就开始与同赛区队伍以及校友测试了,当rOtp和我讲“你们这是冠军代码啊”时,还有那么点小激动,不过同时也意识到,大家可能还误解着这个题目,没搞清楚压制是最强的武器。这是博弈,不是收益。
当时虽然赢了几个人,都能达到占领消费节点数两倍的场面优势,但是代码还处于不会存钱的阶段,除非对手也不会存钱,否则都是输。这天晚上记忆特别深刻,和追日大佬相约怼到半夜,互有输赢,最终测出来从45回合开始不购入服务器能有最高的收益,还确定了在最后一回合将20回合后购入的服务器卖掉的策略。很遗憾,追日大佬被程序Bug坑了,没能进到8强,最惋惜的一支队伍,没有之一。
整个比赛充斥着假代码,很难找到能够测试的队伍,除了自己赛区就只有rOtp、追日、还有土豆几个大佬帮我测Bug,找问题。
所以我不想藏代码,跟大家共同进步吧。
很深刻的记得,土豆在和我pk好多次之后对我说:“我算是明白了,流量为王啊。”当时的心里真的有了遇到知己的感觉。11 ,土豆他们也终于拿出了新代码,我甚至无法击败他们,于是我也开始努力改进我的代码。于是我发现了我的初始解的重大失误,导致我竟一直放弃了入度小于10的所有消费节点。
特别鸣谢成渝赛区的PPAP,最后一晚帮我测试了新代码的正确性,你们竟然能够写出这么棒的判题器,真是佩服得五体投地。
尘埃落定,我把缩行缩得过分的只有600行的代码调整了一下圈复杂度,却没想到今年换了专家审查的方式。不过肥水不流外人田,最优美代码给了blasting。专家们想必是都懒得看我丑的过分的代码,颁奖会上连优缺点都没有讲,在此我只能说句抱歉,实在是时间紧。
等我想到“将被对手占领的点设为两倍回 ”这个策略是谬误的时候,已经是第二天了,如果修正了这个策略,想必也不会被偷鸡了。
六、比赛总结
(这段不想看就不看吧,没有赛题相关内容)
最终没给旭神和三人游丢脸,拿了个自我感觉良好的成绩。更重要的是,我的友军们也都凯旋归来,土豆还拿了两块奖牌。虽然被某人暗中针对了一下,总的来说算是皆大欢喜了。给我带来帮助的所有人,谢谢你们。
考虑到不太可能再写一篇了(再次郑重声明,不是懒),那就在这里总结一下收获,并小小地煽情一下。
难忘认真审题的第一天,
难忘白天黑夜埋头苦干,
难忘榜单成绩零的突破,
难忘首条朋友圈纷纷点赞。
难忘QQ群聊一刻不闲,
难忘哲哲的邪恶表情脸,
难忘神秘代 “xjbs”,
难忘与惠惠女神家长里短。
难忘旭神的耐心负责,
难忘三支队伍勇往无前。
难忘武研所风光无限,
难忘斗地主彻夜无眠,
难忘列车上恬静睡颜,
难忘最后三分钟反杀压线。
难忘武长君的摸头杀,
难忘吉哥的侃侃而谈。
难忘小浪花水立方新的远方,
难忘为了武长荣耀而战。
难忘校友的王者归来,
难忘zc的真知灼见,
难忘与土豆惺惺相惜,
难忘百折不挠的追日少年。
难忘吉祥物宝宝小原,
难忘CodeCraft 2017年。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!