先貼上我們名次,我們是上合賽區的【上江湖南西海】隊。初賽38名,復活賽8,然后就game over啦:
這次的賽題依舊涉及圖論相關的算法。去年是【未來網絡:尋路】,今年是【大視頻時代:布局】。連續兩年都是圖論了,建議明年準備參加比賽的同學可以先把圖論給溫習了。
細分的話,這是一個CDN問題,或者叫做server placement/ facility location問題,總之搜這幾個關鍵詞可以拿到很多有價值的論文。建議明年準備參加比賽的同學,拿到賽題后先把問題模型搞清楚,確定其在學術界的名稱,然后就可以去搜論文了。看論文是一個非常重要的集思廣益的過程。
簡要地給出我們的timeline:
1.一開始打算借鑒去年的線性規劃。
比賽前期遇到期末考試,斷斷續續用了一周在Lingo把官網case跑出來了,得到最優解783,但是耗時4s,時間太長了。又嘗試自己寫單純形法來解整數規劃,逐漸認定手寫至少很難和商業上成熟的軟件相比,耗時問題難以解決,遂放棄。后來網上有人分享,他用整數規劃成功解決大case,用時超過2小時,而解決QQ群里面非官方的終極case則需要耗時一天以上,這條路的確行不通。
2.大事化小,先解決CDN位置確定時的最小費用最大流問題。
謝師兄成功寫出了SPFA算法,這個是我們比賽過程中的一個關鍵點。接著我加入超級源點和超級匯點的思想:超級源點連接設定好的CDN(為了方便,接下來服務器都會被簡稱為CDN),超級匯點連接所有的消費節點所連接的網絡節點,并且保證所有消費節點連接到超級匯點的鏈路帶寬即為其帶寬需求。至此,當確定某幾個節點為CDN時,可以求出最大流為且只能為固定值時的最小費用問題(后期注:另外據可靠消息,zkw算法比SPFA更適用于此圖)。所以接下來我們只需要想方法來確定CDN位置。
3.用遺傳來確定CDN位置。
當時擺在我們面前的主要有退火、遺傳這兩種啟發式搜索方案,我們選擇了遺傳算法。現在回想起來,這個選擇注定是一條更難的路:首先在選擇初始種群時走了彎路,嘗試了很多種方法來篩選出"CDN候選點",也就是篩選出哪些點可以作為基因,想來減少后期的計算量;其次種群的迭代太慢了,迭代出來的也不一定是可行解;并且遺傳有個關鍵:要保證產生的子代很大幾率上是優于父代,才能保證整個過程是在優勝劣汰。這個關鍵點我們沒有處理好,進一步減慢了迭代速度。
4.改用退火算法
后來寫出了一版退火算法,立馬就上64強了,在50-60名之間。退火更加適合于這道題,我認為關鍵原因在于:退火的求領域解的機制加大了產生可行解的幾率,使得迭代的有效率大幅提升。遺傳不是不好,而是比起退火來難很多,導致我們一直沒有調試出滿意結果,當然也可能是我們寫殘了。一打聽周圍的參賽同學,才發現那些早就順利上榜的人用的都是退火算法。
回過頭來,我反思為什么我們在遺傳上浪費了太多時間: 不舍得丟棄已有成果,覺得不斷努力肯定會有好結果。實際上,應當具備一定的預見能力,覺得行不通盡早換方向。鍥而不舍,及時止損,這兩者的關系要好好把握與權衡,這可能就是大佬經驗比我豐富的地方吧。當然止損也有一條捷徑,那就是要在比賽中保持信息暢通,不能閉門造車。如果我們早知道大多數人用的都是退火,自然而然就會更早的轉換方向,畢竟重新寫一版代碼代價太大,沒有大把握的話很難有動力去做。所以建議比賽的同學們應當多結識賽友,沒人愿意白給你指導,你也得拿有份量的信息去交換,這樣得來的信息比在QQ群和博客得來的有用得多。
5.對圖預處理、優化與重構。
既然已經上榜,那么艱難的爬榜之路就開始了。代碼重構主要是師兄在做,每次優化一點兒,名次都能往上爬幾名,但是幾小時后又會有人爬到我們前面來。大家都在優化,不進則退放這里是再合適不過了。我在做預處理的程序,篩選出必定為CDN的點。這個規律很好找,大case能篩選出30-50個點,大大減少了運算量,名詞進入40-50區間。
6.發現重大規律:只需要在消費點直連的網絡節點中篩選即可。
我們之前一直是在所有點中進行CDN選點。直到有一天,看到了有一個同學分享的用線性規劃得到的最優解運算結果。我對他的數據統計分析了一下,發現最優解的CDN位置基本均為消費點直連的網絡節點,偶爾有一兩個落單的點為普通的網絡節點。為了效率,我覺得暫時可以直接在消費點直連的網絡節點中進行CDN選點即可。因為這個規律的發現,再加上對圖預處理的過程,名次進入了30-40區間。所以無論通過什么方法求解,也許是暴力大法,也許是線性規劃,雖然這些方法不可以用在最終代碼中,但是可以獲得用于參考的最優解。這就相當于做習題時拿到了沒有運算過程的最終答案,從答案往過程推導也是一個獲得思路的好方法。
7.用C++重寫。。。
目前為止TOP64至少是保住了,綠卡問題不大了。接下來都在優化與重構,發現Java效率實在底下,迭代次數比C++慢了五倍不止。官方說法是Java和C++差異可以忽略,這個只能參考,千萬別采納。C++作為競賽語言是有原因的,道理我們都懂,依舊走了彎路,萬事都是如此,你不體會一遍就不會長記性。
復活賽競爭異常激烈,復活賽最終的前四放初賽是可以進前15的(此數據僅供參考,并不嚴謹)。所以能在初賽完成的目標,千萬不要拖到復活賽。用CPP重寫后,初級和中級接近滿分了,高級case實在沒時間調試了,若放初賽這結果進前32是穩的。不過沒什么可遺憾的,大佬們的確很厲害,希望自己多汲取教訓,多掌握經驗。
最終我們的代碼構成為:退火算法尋址 + SPFA算法求路徑 by C++語言。附上github源代碼以及目錄:
數十個賽區,只有西北川渝等賽區的競爭才算是白熱化,尤其是成電、西電等高校長期30多個隊伍霸屏TOP64榜,而其他賽區進TOP64和拿綠卡難度并不大。所以我明年肯定會推薦學弟學妹們來參加這個比賽,性價比還是很高的。
比賽過程中,偶遇開發者論壇愚人節活動。運氣比較好,我們隊三個人拿了兩套華為code craft衛衣和小原公仔。感謝我的隊友:謝師兄和任同學,尤其感謝他們給我做隊長的機會,比賽完帶隊友們去赤坂亭吃了一頓飯,放張照弱弱地紀念一下:
寫下這篇文章,主要是給自己隊伍的工作做個總結,也給其他和我一樣的"小白"提供參賽經驗。歡迎關注,歡迎討論。