還差說好的Facebook Hacker Cup 2015 還有我又走了數的Rockethon@Codeforces...
話說這場#285, 雖然是Div 2...
可是題目難度不一樣...(是指C開始的題目)
C很快想到算法可惜在實作上碰釘碰死了
D到現在我還是覺得不可能現場即時做到的...除非那個人即時Google 看了Wiki 再秒速理解+實作那個Algorithm...
E的AC人數太可怕了, 我連題目也沒看...
那麼開始吧
Codeforces Round #285 (Div. 2)
A: (頹題)
就是代數入公式做一些比較之類的...
B: (String, STL)
String 與Map (背後思路也有DFS的份兒) 的基本應用實作...C: (Graph, STL)
開始有趣的一題...
Given n (<= 2^16) 個node 的 degree 跟 XOR sum of its neighbors' ID
node的 ID 是由0 到n-1
求符合該條件的圖, print 出該圖的 edges (任意次序)
題目不算很直接的...倒也不算很難
題目不算很直接的...倒也不算很難
首先 答案的圖不一定connected, 可能有node 是完全沒edge的也不奇怪 (雖然對算法沒影響)
然後很自然地就會注意到題目設定的 2^16 跟XOR sum..
完全指向bit operation嘛...
由於XOR的Associativity ( (A^B)^C = A^(B^C) ), 然後 A^A = 0 即 A^0 = A
所以它是可以"Reverse"的 (可以這樣說嗎..)
(A^B)^B = A^(B^B) = A^0 = A
(A^B)^B = A^(B^B) = A^0 = A
那麼注意到, 我們已知題目內每一個node 的XOR sum A1^A2^A3^...^A_n
假設我們肯定了它其中一個neighbor 是A3
假設我們肯定了它其中一個neighbor 是A3
那麼我把可以把 XOR sum 去走A3 的部分:
(A1^A2^A3^...^A_n) ^ A3 = (A1^A2^A4^...^A_n)^(A3^A3) = A1^A2^A4^...^A_n
如此類推 最後該node的XOR sum只剩一個A_i, 那麼A_i 就是它最後的neighbor
問題是怎樣找出它第一個neighbor呢?? 其實答案已經出來了, 只是要逆轉思維
我們把題目的Input 排好序, 以degree少的優先處理
degree = 0 就跳過吧, 根本沒有neighbor...
degree = 1 才是我們要的!
degree = 1就是說該node 只有一個neighbor, 那麼它的"XOR sum" 不就剛剛是它唯一的neighbor id嗎??
然後我們把它這一個neighbor的XOR sum 除去自己的id (用上面 xor 一次的方法)
同時把它的degree - 1
repeat 直至 所有node 的degree 都少於1就完成了
時間複雜度呢? 由於每個node只會選擇一次, 但是每次處理完一個node都要把update完degree的nodes 重新排序...也不可能每次都sort 一次吧??? 考慮上只剩下每次都自動以 O(lg N) 排好序的方法了.... 有點像Dijkstra Algorithm的影子呢
所以自然! 就是想起heap 這個神奇的data structure, 實作用的就是STL的 priority_queue了吧
老實說, 想到這個做法的時間還是很短的...實作起來才是痛苦的開始
應該是我太久沒用過STL的priority_queue吧, 我發現我是不懂實作customize 的 compare function的...google了一大輪, 無限試驗後, 才有一個像樣點的寫了出來
bool compare(int x, int y){ return deg[x] > deg[y]; } priority_queue<int, vector<int>, decltype(&compare)> w(&compare);
當然我印象中是絕對沒那麼複雜的...看了Gary神與Peter神的實作也沒那麼複雜, 這題真的感受到除了思路之外, coding的技巧還是太嫩了=.=
來學習神人Gary的寫法:
struct node{ int id,deg,s; node(){} node(int id,int deg,int s):id(id),deg(deg),s(s){} bool operator<(const node &d)const{ return deg > d.deg; } } ss[100000]; priority_queue<node> q;
人家輕鬆寫個struct再override operator就行了, 寫反了的原因是STL priority_queue好像default是Max. Heap來的, 所以要人手把degree少的當成較高priority讓它們排在前
還有一件事也是煩了我很久的....可能我中了幻術,
我總以為這個priority_queue是online auto sort的...也就是說每次裡面的element有update也會自動再sort一次...當然這是不可能的, 太理想了...
所以要完成上面的算法, 實際上是每次update degree後, 看看degree變成1了沒有,
如果沒有就別管了, 完全不處理, 反正最後一定會有它的一個neighbor把它的degree降成1 的時候, 而那時候我們再push 一個新的degree 為1的node進去priority_queue (O(lg N)的時間)
就好了, 這樣說的話,實際上每個node就不止access一次了, 更可能是2次 (第1次發現degree還沒變成1, 可是又已經到了heap的頂了, 直接拿走; 直到我們再push它進heap底, 這時它必定是degree 1了)
總的來說還是O(N*lg(N))
D: (Math, Binary Indexed Tree)
我就直說了吧, 這題根本不可能做到的...要不是我即場做的時候有點眉目, 不甘心下跑去看Editorial, 根本不會發現這題所需要用的冷知識...
當然還是很感謝它的, 令我又再跑去學了Binary Indexed Tree (BIT)一次...
先來說說題目吧, 題目是很簡單易明的
Given length N 的permutation 兩個 p,q.
然後每個permutation 都有各自的Order, 代表它們lexicographical 的排名, 由0到N!-1
定義permutation的sum為 (Order(p) + Order(q))%N!
找出排名為 sum 的permutation
這題的變態位在於 N <= 200,000 (O:*2036
先不說別的, 只是要執行 mod N! 這一步已經不知怎做了...
第一次做的時候我還是沒放棄的, 我姑且去解Order()這個function
這個還是勉強靠一些combinatorics 可以count出來的~
設p = (3,4,2,1,5)
我數的是有多少個permutation比它少對吧?
那麼第一個數字比3少的肯定要數了...(1,?,?,?,?) 跟 (2,?,?,?,?), 各自都是 4! 個可能性
我數的是有多少個permutation比它少對吧?
那麼第一個數字比3少的肯定要數了...(1,?,?,?,?) 跟 (2,?,?,?,?), 各自都是 4! 個可能性
然後即使第一個數是3, 也有可能第2個數比4少的, 注意比4少的卻不能是3, 因為3已經在第1個位, 那麼有(3,1,?,?,?) 跟(3,2,?,?,?) 各自都是3!可能性...
注意到了嗎?? 其實比一個permutation少的permutations數目, 就是Editorial說的
(應該是(k-1)! 才對?)
當中 d_k 是比第k 個數字少,而且還沒出現的數字的可能性, 換句話說,
是比第k個數字少, 又在第k個數字右面的數字 數目
然後乘上 (k-1)!
我找了些N很少的permutation來算, 也是對的...算是可以開心吧
問題是...這個方法去找Order, 也是避免不了要算 N! , 你媽啊...
我也有想過會不會 mod N! 有些我不懂的方法是不用直接算出N!的...
可是連Order()這個function也已經要用上N!了啊...
Peter神的指引下, 還是跑去看Editorial了...一看之下可真是嚇呆了
這個奇特的Number System竟然存在的啊...而且它跟permutation還有一種奇妙的關係...
而這條題目就是要利用那個關係啊啊啊啊啊啊.......
誰會懂這個非主流的東西啊....
好吧...我只能說這個Number System 我也沒看完, 但由於也是一個Number System,
還不算難理解的
正常的Number System, 例如十進制跟二進制
每個位數也是 10的次方, 或者2的次方, 第 i 個位 (0 <= i) 就是 coefficient 乘以 10^i 或者2^i
其它進制也是這樣
其它進制也是這樣
而Factorial Number System則把"次方" 改為 Factorial...
就是 第 i 個位 是coefficient 乘以 i ! .....
然後談進位的問題, 十進制每個位置不能大於9, 二進制不能大於1...
又Factorial 進制則是 每個位置都不同, 第 i 個位不能大於 i
舉個合法的例子 341010! = 3×5! + 4×4! + 1×3! + 0×2! + 1×1! + 0×0! = 46310
十進制轉換Factorial進制就跟轉二進制差不多, 也是通過連續的除法取其餘數
詳情wiki 很清楚
好了, 要完成題目, 必需要用到的是Permutation那一節, 原來這個Number System跟Permutation有個神奇的關係, 就是它可以直接找出lexicographical = 該number 的permutation!
看例子最實際了: 4041000! (equal to 298210)
(0,1,2,3,4,5,6) 的第 2982 個 permutation 是什麼呢?
Factorial 進制的每一個數字, 都代表 (0,1,2,3,4,5,6) 剩下未用的數字位置!
例如4, 就是第4 個數字 4, 然後我們把4 踢走: (0,1,2,3,5,6), 答案: (4,?,?,?,?,?,?)
再來是0, 就是第0個數字0, 加入答案, 然後踢走: (1,2,3,5,6) 答案: (4,0,?,?,?,?,?)
例如4, 就是第4 個數字 4, 然後我們把4 踢走: (0,1,2,3,5,6), 答案: (4,?,?,?,?,?,?)
再來是0, 就是第0個數字0, 加入答案, 然後踢走: (1,2,3,5,6) 答案: (4,0,?,?,?,?,?)
再來是4, 就是第4個數字6, 加入答案, 然後踢走: (1,2,3,5) 答案: (4,0,6,?,?,?,?)
如此類推....
竟然可以這樣真直接找出來了....!
那麼是咁的, 把這個轉換逆轉, 就可以把一個 permutation 直接轉成 Order了 !(以Factorial Number的形式)
事實上, 不論由Permutation 換成Factorial Number還是掉轉, 也是要用到BIT的...詳情再說
假設我們成功獲得了Input p跟q的 Order了, 就是有兩個Factorial的數字了
如何算出它們的Sum???
加法是沒問題的...我要再強調, 它也算是一個Number System, 照樣理解的話,
加法是沒問題的...我要再強調, 它也算是一個Number System, 照樣理解的話,
就可以linear地手動進位去算加法了
問題是 modular N! ....
首先要發現, 兩個數加起來, 肯定不會超過2N! , 就如十進制的 9+9 也不會比20大吧..
對了...十進制的十, 就等於 Factorial 進制的N!, 這樣理解很好
所以modular N! 跟減N! 在這裡是一樣的..
然後! 事實上由於N! 不可能以 N 個位數表示的, 想一想就知道了, 剛好要進位的嘛!!
所以N! 根本就一定是 10000....000 的形式存在啊! (跟二進制的 2^x 一樣, 10,100,1000000...)
所以....modular N! = 減N! = 其實就是兩個Order加起來 ignore 第N 個位就好了....
這是何其聰明的做法啊...
最後把這個SUM 再換成permutation就好了...
太淫賤了, 正常人根本不可能懂的嘛...
我們最後來看一下最重要的部分:
- Permutation --> Order (Factorial Ssytem)
- Order (Factorial Ssytem) --> Permutation
首先要先溫習一下BIT....
以我最新的理解...BIT跟Segment Tree最大不同之處, 是簡單易code
同時也很多限制, 好像只能作一些 accumulative sum的工作 (借此可以達成 range add x 的效果)
例如Permutation 換成Order, 其實我們每看一個數字時, 我們要知道比它少的數字已經出現了多少次 (踢走了多少個)
用同一個例子 (4,0,6,2,1,3,5):
看4時, 我們把BIT[4..N] 都加上 -1, 看0時也把 BIT[0..N]加上-1
用同一個例子 (4,0,6,2,1,3,5):
看4時, 我們把BIT[4..N] 都加上 -1, 看0時也把 BIT[0..N]加上-1
看6時, 我們其實Query了 BIT[6], 發現是-2了, 所以我們實際儲存6-2 = 4, 當然也要BIT[6..N] 加上-1 如此類推
int sum(int x){ int ret = 0; for(x;x; x-=x&-x) ret += bit[x]; // 找出[0, X] 的accumulative sum return ret; } void update(int v, int x){ for(x;x<=n; x+= x&-x) bit[x] += v; // 把包含 x 在內的樹加上v } void toFac(int *arr){ memset(bit,0,sizeof(bit)); for(int i=1; i<=n;i++){ int tmp = arr[i] + sum(arr[i]+1); // eg: 位置6 加上 [0,6]的accumulative sum(-2) = 4 update(-1, arr[i]+1); // 不忘update, 把現今位置及包含現今位置的樹加上-1 arr[i] = tmp; } }
最後了, 把Order 算出Permutation來
這個反而有點tricky, 是要用上Binary Search 加BIT的
思路是找出第一個 位置+BIT[] = Search Value 的位置
要強調第一個是因為可能有多個出現, 例如
( 0, 1, 2, 3, 4, 5, 6)
(-1,-1,-1,-2,-2,-2,-2)
如果我們要找 "1"的話 (現今Factorial Number的數字)
(2-1) 跟(3-2)都是 1, 我們要取2還是3?
答案是肯定取最前的, 因為後面那個(那些) 會減至跟前面一樣只有一個可能性: 它們已經先被取掉了, 然後update了, 所以才會減那麼多次變成跟前面那些一樣
答案是肯定取最前的, 因為後面那個(那些) 會減至跟前面一樣只有一個可能性: 它們已經先被取掉了, 然後update了, 所以才會減那麼多次變成跟前面那些一樣
但由於它們不論怎樣減, 也不會被前面位置+BIT少, 就是說 (位置+BIT) 是長期處於Sorted狀態
所以才能用Binary Search
老實說, 在看了wiki之後, 獲得算法之後, 這部分算是最難搞的 (腦內清楚卻很難表達...)
老實說, 在看了wiki之後, 獲得算法之後, 這部分算是最難搞的 (腦內清楚卻很難表達...)
這題的感受是...什麼Factorial Number System, 不打算詳細記下...倒是BIT的用法跟實作
可能要找個時間做一下啊...可能是個很強大又性價比高的武器
沒有留言:
張貼留言