2014年12月21日 星期日

Codeforces Round #277.5 (Div. 2)

說好的第二場近日做的題目
這場只有div 2的, 題目也意外地有6題, 暫時解了5題, 第6題還沒看題目..
做到再補完吧,,

Codeforces Round #277.5 (Div. 2)


A:
頹題, 但一開始是嚇死我了
題目要找出把一個sequence sort好的swap 次序,
我開頭以為要找最短的次序,,,
原來我瞎了看錯題目, 明明就是寫 "you do not have to minimize the number of swaps"...

B: (Greedy)
半頹題
題意如下:
Given n 個男仔, m 個女仔, 每個人有個value, 如果男女value difference <= 1 可以pair up
問最大pair up #

Greedy地 maintain 2支pointer pair up 最大value的就好...


C: (DP? Ad-hoc? Greedy!)
Fuck, 這條才是麻煩, 我從看題目就中伏了幾次
題目求找出長度為S 的數字, s,t, sum of digit = N
最小的數字最大的數字是什麼, 沒有則print -1

從數據上, 我最直接當然是想到DP了...其實DP好像也真的能做到的樣子
可惜最後我用了個較constructive的方法 (而且code也有點煩膠)

要我說的話是Greedy的做法
其實就是先把每個位置都填上 N/S (average), 然後看是找最小的數字還是最大的
把頭尾的位置, 一個+1, 一個-1, 直到boundary (0 或者 9) 就移動pointer 去改下一個位置

但中伏位還是很多的, 例如第一個位置不可以是leading zero, 但中間的位置可以是0 吧
由於這個特性, 找最小和最大的數字 不太一樣做法
例如 長度2, 總數為8的答案就是 17 和 80

一個loop就做到了, 所以是O(S)


D: (Graph, DP)
還是很頹的題目

題意是這樣的, Given 一個有向圖 (不肯定是否DAG, 也不重要)
n個node (n <= 3000)
找出「吃屎菱形」的數目, 該形狀定義為:
由A到B的path中,  有兩條path為 ACB 和ADB
那麼(A,B,C,D) 就是一個「吃屎菱形」
C和D的次序不重要 (在數菱形數目的時候)

很直接就想到去DFS了吧 (尢其有上一場某題的經驗...)
話說就是DFS, 說它有DP的成分是因為你需要儲下一些資料而已, 但其實這些資料
不需要什麼子問題就可以計到, 所以又不能說是DP...不管了

總之就是define dp(n,m) 為 n 到 m 長度為 2 的path 的數目
那麼 dp(n,m) choose 2 就是以n,m為end point的「吃屎菱形」數目

話說由於DFS的深度也只有2, 就是說不怕煩的寫for loop 也行
複雜度也是O(n^2)

void dfs(int r, int c, int d){
    if(d == 2){ dp[r][c]++; return; }
    for(int i=1; i<=n; i++) if(g[c][i] && i != r) dfs(r,i,d+1);
}

int main() {
    scanf("%d%d", &n, &m);
    for(int i=0; i<m;i++){ scanf("%d%d", &a,&b); g[a][b] = 1; } 
    for(int i=1; i<=n;i++)dfs(i,i,0);
    for(int i=1; i<=n; i++) for(int j=1; j<=n;j++)
        ans += (dp[i][j] * (dp[i][j]-1))/2;

E: 沒看題目...

F: (DP, Combinatorics)

最有成功感的一題, 因為最難=.=
題意是這樣的:
有一個n x n 的square matrix, 全部都是0 或者 1
然後該matrix 每一row 和每一column 都必定有(和不多於) 兩個1
現在提供了頭m row (m <= n) , 問餘下的rows任填的情況下, 有多少種valid既matrix?
當然又是mod 一個大數
n <= 500

糾結啊...
DP是DP的了, 又想不到狀態...
Combination怎麼數?  曾經不是有些心得的嗎...
又沒什麼頭緒...

在紙上畫呀畫, 突然運氣又到了
不知為什麼會走去想到 "每一column 還需要填'1'的數目" 這件事
而且這個就是重點了!

別管DP的部分, 直接看數combination的話怎樣數呢?
基本上, 「每一個column還需要填 1 的總數」只有{0,1,2}
0 就是說根本不用再填了, 完全不用管, 剩下{1,2}兩種

好像有點煩, 所以直接想全部都是1 的情況:
每一row也一定要有2個1 的,  而我每一個column的「彈藥」也剛好是1
就是說我用完某一個column, 其它row就不能再用了
所以第一row (第一個我要填的row) 是 nC2 的可能性
然後第二row 是(n-2)C2 的可能性..
直到2C2

回到有{1,2} 兩種「彈藥庫」的情況, 把問題換個角度形容吧~
就是有n 個箱子, 有些箱子有一個球, 有些箱子有兩個球,
每次要抽兩個球, 而且不能從同一個箱子抽, 抽完把球扔走
總共抽 n-m次 <--這個其實不重要, 總之直到球抽完就好
有多少種不同的抽法?

我忽發奇想, 由於全部箱子都是一個球的情況上面已經處理到了
有沒有機會把現在這個問題也化為全都是一個球的問題/子問題處理呢...
答案是YES!

對於第一次抽球 (就是填第一個row), 我可能的選擇只有三種 (是說三個類別, 不是只有三個可能性!)
1. 從兩個「裝有一個球的箱子」各抽一個
2. 從兩個「裝有兩個球的箱子」各抽一個
3. 從一個「裝有一個球的箱子」和一個「裝有兩個球的箱子」各抽一個

發現了吧!  「裝有一個球的箱子」和「裝有兩個球的箱子」的數目我是知道的
然後每次抽球只有這三種可能性, 每次抽完會影響到這兩種箱子的數目
然後再抽....變成子問題了! (箱子數目減少了!)

轉化為DP去實作
Define dp(n,m) := 有n 個「裝有一個球的箱子」, m個「裝有兩個球的箱子」, 直到抽到沒球可抽的所有可能抽法總數

dp(n,m) = nC2 * dp(n-2, m) + mC2 * dp(n+2, m-2) + n*m*dp(n, m-1)

當然吧, 每個component都要先查一下是否真的valid, 例如最後的component
雖然好像只要m-1 非負數, 但實際上他代表的是從兩種箱子各抽一個球
所以n 也要 >= 1

最後處理好base case, dp(0,0) = 1 吧 <---這個其實就是說所有row input都provide, 無野填=.=

提交吧! TLE!!! WTF!!!
喊出黎, 我完全唔知點解...

最後終於發現....是這兩句

if(d[N][M]) return d[N][M];

累事啊..
因為...因為題目是要mod一個大數,  mod完之後答案的確可以是0的, 那麼這些dp state永遠不能reuse了...當然TLE 啊=.=

把初始值改為-1, 再判定為非-1
 if(d[N][M]!=-1) return d[N][M];

AC了!!!!
複雜度呢, top-down要solve recurrence relation的說...
說笑呢...bottom up + logically n <= 500, 所以是 O(n^2)


2014年12月20日 星期六

Codeforces Round #277 (Div. 2)

轉左新公司, 雖然工作和興趣已經慢慢轉移到.NET技術及Unity Game Design,
閑時仍然會做下題目, 發現斷斷續續做左唔少, 年代久遠完全沒印象
只好挑近幾日做的兩場...

話說已經無認真做 (2小時內完成)既態度, 有d題目甚至分開幾日做
我諗做到就好了,  近黎都保持div 2 做到4-5題, 算唔錯了...

Codeforces Round #277 (Div. 2)


A:
頹題, 如題意所示, 計一個簡單function 的instance f(n)

B:
出奇的略有煩膠的題目...solution倒是很直接的
AC後略略看過別人的code和editorial, 發現好像不能避免煩膠的寫法

題意如下:
Given 一個 m x n matrix A, 每格either 0 或者 1, 求reverse engine 一個matrix B,
s.t. A_i,j  = BITWISE_AND( B_i,x ,  B_y,j) for 1<= x <=n,  1<= y <= m

因為m , n 都很少 (< 100) 根本brute force就做完了  但很難避免3-4個loop的寫法...嗯

C: (Greedy)
真正既第一題, 題意如下:
Given 一條string s (length <= 10^5) 和 一個指針的位置 p,
每步可以做2種操作, 問minimum # steps
s.t.  s 變成一條palindrome

操作 1 是指針向左或向右移動一格, 然後這兒的移動是wrapping的,
即最左再左就是最右; 最右再左就最左

操作 2 是指針位置的字母加一或減一, 也是wrapping的,
即a 再減一變z ; z 加一變a

s 確定所有都為lowercase

題意很有趣, 但看完有嚇到...
諗真d 又唔係咁複雜, 首先不難發現操作1 跟操作2 是沒有關係的

來看看操作 2:
因為現在不同edit distance之類的設定, 不能加減字母, 而只能將現有字母一步步改變
然後按住palindrome的特性, 如果位置 x 跟 位置 x_reflection 的字母不同, 你一定要隨便
轉變一個令他們一樣, 轉那個都沒所謂, 反正你轉字母的cost (# of steps) 是定好的
跟移動的cost (操作 1)完全無關, 也就是可以分開precompute的意思

再來看操作 1:
不難想像, 根本不會出現wrapping的情況, 來看看其中一個情況:

_ y p _ x | x' _ p' y' _

p 為現在指針的位置,  x,y 跟x' , y' 是相對的字母位置,  | 沒特別意思, 是字串中間的分格線而已
想做的是, x 要變成跟x' 一樣, y 要變成跟y' 一樣, 這是操作2的cost, 但移動的cost 又怎算呢?

腦內生成一下可能的移動方法, 不難發現指針根本不會以任何方式移動到另一半
不然移動的cost會增大, 不去另一半就是最好的了, 這是greedy的思路
(這應該是能證明的, 實際上在公司裡我感覺能行就做了, 沒詳細證明...)

小總結: 操作1 跟 操作2 直接分開算, 實作的時候只要考慮指針是先向左走還是先向右走比較優就好了, 時間複雜度為 O(N)

D: (Graph, DP, Combinatorics)
這題有點興奮, 以前應該做不到吧...
雖然思路不知怎的突然走出來, 說是運氣都不為過就是了...

先來說說題目:

Given 一個 node數為 n (<= 2000) 的tree, 一個數字d (<= 2000)
每個node有一個value (<= 2000)

定義valid set 為一棵sub-tree, such that 該tree內的max. node value - min. node value <= d
求 valid set 的總數 (再mod 一個大數)

有點黃有點暴力, 開頭真的有點頭痛...
以前看見tree都有不好的感覺, 話說這次當然也是第一時間跑去想dp...
想想想, 好像不太能想到一個好的state

然後在紙上畫畫畫...又好像簡單的DFS能解決的樣子?
又怕會double count...

double count?? 突然靈光一閃, 運氣到了, 如果我先fix死一個node,
然後先找所有包括該node 的valid set...然後每個node都做一次, 不就不會double count了嗎?

然後..然後...不如直接把該node定為最大的node, 啊我是說
Fix 一個node n, 然後找出所有包括node n, 而且 node n 的value就是最大的valid set!

這個好像DFS能行...只要我search到一個node的value大於 root 的value, 或者 abs. difference > d
就return, 不然就一路search下去..

<1999>
假設一個root 有3個children, A,B,C;
# Combinations就是 (DFS(A)+1)*(DFS(B)+1)*(DFS(C)+1)了
要+1 是因為可以整個children (sub-tree)都不要, 只要current node自己
</1999>

試一點test case都過了, submit!

WA!!!!!! WHY!

原來...忘了處理value 一樣的問題...
就是仍會double count的意思...
例如
我以value為10的node A開始做DFS, 他其中一個child B也是value 10, DFS會一直走下去
這是第一次DFS

第二次DFS我用這個child B為root做DFS, 也一樣會數包括了A的sub-tree, 那些我已經數過了, 我只想數一些不包括A的sub-tree...

這樣解決了double count的問題, 也AC了...
時間複雜度是 n 次DFS就是O(n^2)
糾結的是problem tag是寫DP的...怎用DP做呀??

LL dfs(int c, int p, int root){
    if(a[c] > a[root] || (a[root]-a[c]) > d) return 0;
    if(a[c] == a[root] && v[c]) return 0; 
    //這就是處理double count的問題的一句
    LL ret = 1;
    FER(i,1,n) if(g[c][i] && i != p)
        ret = (ret * (1 + dfs(i,c,root)))%C;
    return ret;
}
 FER(i,1,n) {
        ans = (ans + dfs(i,-1,i))%C;
        v[i] = 1;
    }

E: (DP, Greedy)

好吧, 這條我思路是完全錯誤的, 我是看了editorial 也不想code這題的廢物
(因為它說要segment tree / BIT...)

幸好我去看人家紅字User的comment, 很多User也有一些純logic的做法, 
完全不用什麼特別的data structure啦...

先來看萬惡的題意:
LIS, 大家都知道, 而現在Given 一條sequence (長度 n <= 10^5)
把每一個數字歸類為:
1. 不屬於任何LIS
2. 屬於某些(但不全部)LIS
3. 屬於全部LIS

就是這樣...很邪惡

LIS加上這個數據量, 我自作聰明的就把思路轉去那個binary search O(n lg n)的
做法, 看看再加工會不會做到...
後來想得好複雜, 還真的code了出來, 還WA了 哈哈 = =
好吧, 我要承認, 如果不懂binary search 找LIS的做法也是做不到這題的
為此我還故意找我以前的post來回憶一下 =.=

以下為睇完某紅字User comment既解法, 
基本上code是很易的...

話說第一步就已經想不到了...
設A[i] 為 以 第 i個數字(一定要包括它) 為結尾的LIS 長度
設B[i] 為 以 第 i個數字(一定要包括它) 為開首的LIS 長度

Editorial的話, 就要用segment tree / BIT 數它們的總數之類..

紅字User的高明得多...
首先A[i] + B[i] - 1就是包括第 i 個數字的LIS
如果這個數字不等於整條sequence的LIS, 那麼第 i 個數字一定是屬於第 1類了
然後剩下的數字, 要分類為2或3

重點來了!  (紅字User曰) 若 A[i] 跟A[j] 是一樣的, 他們2個數字, 可以於任何LIS中互相取代
所以他們2個都一定都不是屬於所有LIS, 即類別 2

剩下的(就是A[i]是unique的) 就是類別3...

而以上所有步驟都可以用類似binary search找LIS的方法去實作...
所以基本上時間複雜度也是O(n lg n)...
實作上由於I/O的關係, 不能用STL:SET做, 要人肉binary search...很煩的說..

這些紅字User只能說

太聰明了, 太強大了!