#
[2010],74.5(+5+7-5), 市排10-
[2007],49(+24+3+4), 過線8分
[2006],75.5(+8), 市排0, 省排29
[2009], 76.5, 市排10-
[2008], 79(+17), 市排10-, 省排70+
有兩套題是按照初賽模式模擬的, 連續思考還是有一些影響, 不妨在考場上先休息一下.
1. 小數進制轉換
(1) n進制 -> 10進制
(A1.2)16 = 10*16^1 + a^16^0 + 2*16^-1
[長除法格式]例如,1020304從10進制轉到7進制:
1020304 / 7 = 145757 r 5 ↑ => 11446435
145757 / 7 = 20822 r 3 │
20822 / 7 = 2974 r 4 │
2974 / 7 = 424 r 6 │
424 / 7 = 60 r 4 │
60 / 7 = 8 r 4 │
8 / 7 = 1 r 1 │
1 / 7 = 0 r 1 │
http://zh.wikipedia.org/wiki/%E8%AE%B0%E6%95%B0%E7%B3%BB%E7%BB%9F#.E8.BF.9B.E5.88.B6.E8.BD.AC.E6.8D.A2
(2) 10進制 -> n進制
除n取余, 乘n取整
http://www.cnblogs.com/keycode/archive/2010/10/26/1861265.html
2. 邏輯表達式
(1)考慮四種情況代值
(2)利用邏輯代數公式化簡
http://zh.wikipedia.org/wiki/%E9%80%BB%E8%BE%91%E4%BB%A3%E6%95%B0
http://202.201.48.18/jpkc/2006/szdzjs/shoukejiaoan/0001.htm
3.數據結構
(1)表達式樹
[中綴] (a + b) * c + d * c
(((a + b) * c) + (d * c))
[前綴] +(*(+(a b) c) * (d c))
+ * + a b c * d c
[計算方法1] 壓棧(前綴 -> 從后向前), 即彈出順序
[計算方法2] 括號(opt后) -> 中綴
(2)二叉樹中根遍歷
根據先根遍歷和后根遍歷畫出樹, 找到樹中的不變部分, 分析不確定部分的情況(任意與否).
4.計算機史
[計算機領域先驅者]
http://zh.wikipedia.org/wiki/Category:%E8%AE%A1%E7%AE%97%E6%9C%BA%E9%A2%86%E5%9F%9F%E5%85%88%E9%A9%B1%E8%80%85
5.常識
[面向對象程序設計]http://zh.wikipedia.org/wiki/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1
[ALU]http://zh.wikipedia.org/wiki/ALU
[Hash]http://zh.wikipedia.org/wiki/%E6%95%A3%E5%88%97%E8%A1%A8
[IPv6]http://zh.wikipedia.org/wiki/Ipv6
[RAM]http://zh.wikipedia.org/wiki/%E9%9A%8F%E6%9C%BA%E5%AD%98%E5%8F%96%E5%AD%98%E5%82%A8%E5%99%A8
[CPU]http://zh.wikipedia.org/wiki/CPU
[寄存器]http://zh.wikipedia.org/wiki/%E5%AF%84%E5%AD%98%E5%99%A8
[馮諾依曼結構]http://zh.wikipedia.org/wiki/%E8%8C%83%E7%B4%90%E6%9B%BC%E6%9E%B6%E6%A7%8B
[標記語言]http://zh.wikipedia.org/wiki/%E7%BD%AE%E6%A0%87%E8%AF%AD%E8%A8%80
[自然語言]http://zh.wikipedia.org/wiki/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80
6.運算符優先級
http://hi.baidu.com/xyh2007/blog/item/b2cd4b60c5dfa145eaf8f8b3.html
[記憶]去掉一個最高的,去掉一個最低的,剩下的是一、二、三、賦值。雙目運算符中,順序為算術、關系(> >= < <=高于== !=)和邏輯(&& ||),移位和邏輯位(&^|)插入其中。
7.遞推關系
(1)第二類stirling數 s(n,k) = s(n-1, k-1) + k*s(n-1, k)
[直觀解釋]前面一種就是新開一組, 后面一種是前面分了k組, 我隨便找一組丟進去.
(提示:先固定一個數,對于其余的5個數考慮S(5,3)與 S(5,2),再分這兩種情況對原固定的數進行分析)-> 討論一個數
8.補碼表示法
[內容]http://www.cnblogs.com/tenghoo/archive/2008/06/01/1211663.html
[動機]http://blog.163.com/fan_yishan/blog/static/476922132008697421719/
a-b = a+(b % 2^n), 即對2^n取模.
N * = 2^n - N = [2^4](base10) - 0101 = 10000(base2) - 0101 = 1011
[補碼/二補數]http://zh.wikipedia.org/wiki/%E4%BA%8C%E8%A3%9C%E6%95%B8
9.排序算法
[穩定]插入排序、冒泡排序、歸并排序、分配排序(桶式、基數)
[不穩定的]直接選擇排序、堆排序、shell排序、快速排序都是不穩定的排序算法。
[穩定排序]http://www.jpk.pku.edu.cn/pkujpk/course/sjjg/chapter7/01/05.html
[排序原理]http://hi.baidu.com/cuifenghui/blog/item/0587932b039557f9e7cd4051.html
[5個數7次比較]
1. [構造排序樹]http://topic.csdn.net/t/20031207/21/2537867.html
2. 歸并排序(拆分為2 - 3)
3. 從排列角度考慮, log2(n!)上取整
http://zhidao.baidu.com/question/71416468.html
10.圖論
(1)二分圖判定
[定理]無向圖G為二分圖的充要條件是, G至少有兩個頂點, 且其所有回路的長度均為偶數.
(2)哈密頓圖
[定義]由指定的起點前往指定的終點,途中經過所有其他節點且只經過一次.
http://zh.wikipedia.org/wiki/%E5%93%88%E5%AF%86%E5%B0%94%E9%A1%BF%E5%9B%BE
11.閱讀程序注意事項
(1)對程序段進行等價變換
(2)看清if的條件(比較符號看翻, 忽略括號)
(3)兩位數以上乘法豎式計算兩遍(即改變順序) -> 特別注意!!!!!!!!!
(4)注意函數名縮寫(hpsrt)和特征語句(j = k + k) -> 07的堆排(不斷更新大根堆)沒看出來
(5)對于很惡心的模擬題: 列表法(三維可以在水平方向在加列, 不妨用其他顏色的筆, 或者鉛筆指出指針)
12.完善程序注意事項
(1)注意區別遞歸函數參數的0和1,以及遞歸是參數是否需要改變(如果在函數中改變了, 很可能不用)
(2)注意邊界條件(通過交換確定, 還是通過枚舉確定)
(3)遇到比較熟悉, 但是忘得差不多的算法, 一開始不要糾纏, 最后再斟酌
(4)注意函數類型, 比如void main(), 不要寫return 0;
(5)對于for循環, 弄清楚循環變量i或j代表什么(結合題目分析)
13.負數取模
[定義]x % y = x - y*(x/y);
需要注意, 下取整的定義和數學取整的定義不同.
[這里是分析]http://chinakite.iteye.com/blog/25142
14.指針(參照<C++ Primer Plus>)
(1)定義
int a, *b; //這里表示的都是值, &a 和 b 都是地址
int *a = &k; //這么解釋 int * a = &k; 即先對地址a賦值
(2)分析
因而兩種swap可以解釋(只有交換值才有效):
void swap(int &x, int &y){int k = x; x = y; y = k;} //傳入地址, 交換值
void swap(int *x, int *y){int k = *x; *x = *y; *y = k;} //傳入值, 交換值
void swap(int *x, int *y){int *k = *x; *x = *y; *y = *k;} //傳入值, 交換值, 但k是隨機地址, 可能寫到不能寫的位置上
void swap(int *x, int *y){int *k = x; x = y; y = k;} //傳入值, 交換地址
(3)注意事項
int * fellow; //這里的fellow是隨機地址, 必須使用int *fellow = new int;來初始化
*fellow = 123456;
(4)應用: 動態數組(略)
9.7
NOIp03 network[拓撲排序+模擬], 2h
注意讀題, 題目中給出公式C[i] = Σ(W[j][i]*C[j]) - U[i](C[j] > 0), 特別注意成立條件.
把題目中給出的DAG反向, 對于C[i], 計算所有i的后繼即可. 僅按照編號順序輸出大于零的神經元, 題目描述有誤.
*注意分析題目, 不要急于敲程序.
*對于DAG上的拓撲排序問題, 一類如本題和project, 直接利用圖; 另一類如frameup, 要輸出完整路徑, 需要按照定義消去點.
9.8
NOIp01 Car的旅行路線[預處理+最短路], 2h
1.構造矩形, 利用點積判斷垂直, 利用平行四邊形坐標關系求第四點坐標
2.構造圖, 分別處理矩形內 和 不同矩形的情況
3.Floyd最短路, 對始末矩形判斷最小值(此處假設始末矩形不同)
*需要特判始末矩形相同的情況
*注意double的強制轉換
9.10
Tyvj Sept月賽(2.5h)
badgers, 0/1背包, 預計AC.
tree, 排序+亂搞, 看錯題.
沒有考慮深度>2的樹, 似乎需要用到并查集.
number, 模擬, 預計30
顯然top>=2^(n-1)
register, 沒寫, 只會30.
估計150. -> 50;
9.14
badgers, 二分+貪心, 40min, 1Y
對于n只badgers, 需要的食物總量tot = Σ(H[i] + (n-1)*G[i]), 二分求解即可.
*讀題!
*寫出關鍵函數
tree, kruskal變形
利用和kruskal一樣的思想.
1. 對于每個點A分別屬于各自的集合
2. 對邊排序
3. 按順序合并集合, ans += (num[i]*num[j]-1)(c[i]+1)+c[i];
9.26
NOIp10 關押罪犯[二分+BFS判重], 3h
1. 用鄰接表存儲邊, 可以抽象為addEdge函數
2. 二分最大值
3. check函數用BFS染色判重, 枚舉每一個點進行BFS.
*判重可以將數組初始化為-1, 然后判斷隊列中每個點的顏色是否為-1, 可以避免使用vis數組.
[注意事項]
1. 題目中僅給出無向邊
2. 標記節點是否已讀 -> 利用color即可
3. 遍歷所有連通分量 -> 枚舉每個點即可, 不必記錄所有滿足條件的邊上的端點
9.27
NOIp10 關押罪犯, 30min, AC
1. 鄰接表不熟
2. 二分打錯
3. 不理解gXX程序判重原理
[原理]和我的做法一樣, 不同的是gXX程序避免了對每個點重復染相同顏色, 因而需要進行染色的點必然未訪問.
9.28
NOIp10 關押罪犯, 10min, AC
1. 鄰接表first沒有初始化
2. 邊界打錯
NOIp10 烏龜棋[DP], 20min, AC
注意到已通過路徑可以用a+2*b+3*c+4*d表示, 因而設置狀態為f[a][b][c][d];
f[a][b][c][d] = max{f[a-1][b][c][d], f[a][b-1][c][d], f[a][b][c-1][d], f[a][b][c][d-1]} + A[a+2*b+3*c+4*d];
*注意從第一個格子開始, 故A[0..n-1], f[0][0][0][0] = A[0], 注意下標實際意義.
*去年寫錯方程主要是沒有考慮下標間練習, 直接套用NOIp05過河方程. 對于相似的題目, 最重要的是找到不同的部分.
9.29
NOIp08 雙棧排序[DFS+剪枝], 50min, 30
1. dfs狀態: (操作序列長度, 已輸入序列長度, 已輸出序列長度, 棧1深度, 棧2深度)
2. 可行性剪枝:
(1) 優先彈出符合條件元素(即等于 已輸入序列長度+1)
(2) 找到解后立即退出
*標準做法的構造非常神奇, 解釋動機無能. -> 觀點來自gXX
Section 4.1
2010.08.26 TEXT Optimization Techniques
2010.10.19 PROB Beef McNuggets 多重背包+裴蜀定理
2011.08.07 PROB Fence Rails DFS+剪枝
2011.03.24 PROB Fence Loops DFS / 最小環(Floyd)
2011.08.10 PROB Cryptcowgraphy DFS+剪枝+字符串處理
Section 4.2
2011.08.10 TEXT "Network Flow" Algorithms
2011.03.19 PROB Drainage Ditches 最大流(Edmonds-Karp)
2011.03.19 PROB The Perfect Stall 最大流 / 二分圖最大匹配(Hungary)
2011.08.10 PROB Job Processing 二分+貪心
2011.08.11 PROB Cowcycles DFS+剪枝
Section 4.3
2011.08.11 TEXT Big Numbers
2011.08.08 PROB Buy Low, Buy Lower DP+統計方案數+高精度
2011.08.12 PROB The Primes DFS+剪枝
2011.08.12 PROB Street Race DFS判斷連通性
2011.08.11 PROB Letter Game 枚舉+優化
Section 4.4
2011.08.13 PROB Shuttle Puzzle 數學 / BFS
2011.08.13 PROB Pollutant Control 最小割
2011.08.16 PROB Frame Up 拓撲排序
Section 5.1
2011.08.17 TEXT Convex Hulls
2011.08.17 PROB Fencing the Cows 凸包(Graham-Scan)
2011.08.16 PROB Starry Night Floodfill + 模擬
2011.08.13 PROB Musical Themes 二分 / DP
Section 5.2
2011.08.17 PROB Snail Trail DFS
2011.08.17 PROB Electric Fences 模擬退火 / 局部搜索 / 隨機化搜索
2011.08.17 PROB Wisconsin Squares DFS
Section 5.3
2011.08.17 TEXT Heuristics & Approximate Searches
2011.08.18 PROB Milk Measuring DFS-ID + DP / DP
2011.08.18 PROB Window Area 矩形切割 + 模擬
2011.08.18 PROB Network of Schools 強連通分量(Tarjan)
2010.08.19 PROB Big Barn DP
Section 5.4
2011.08.18 PROB All Latin Squares DFS+剪枝 / DFS+置換群
2011.08.19 PROB Canada Tour DP / DP+刷表法
2011.08.20 PROB Character Recognition 統計 + DP
2011.08.19 PROB Betsy's Tour DFS+剪枝 / SCDP
2011.08.18 PROB TeleCowmunication 最小割
Section 5.5
2011.08.21 PROB Picture 離散化 + 掃描 / 線段樹
2011.08.20 PROB Hidden Passwords 枚舉+優化 / 最小后綴
2011.08.21 PROB Two Five Cantor展開 + DFS
Section 6.1
2011.08.21 PROB Postal Vans DP / SCDP
2011.08.21 PROB A Rectangular Barn DP(懸線法)
2011.08.21 PROB Cow XOR 枚舉+優化
大致學習了這些東西:
1. DFS, 以及常見的優化技巧
2. 二分法轉化為判定性問題
3. <string>的使用
4. 離散化思想
5. Tarjan算法(強連通分量)
6. 懸線法求最大子矩陣
7. Hungary算法(棋盤覆蓋, 最小路徑覆蓋)
8. 拓撲排序的DFS實現
9. DP的刷表法實現(白書P172)
10. Edmonds-Karp算法
11. Graham-Scan算法求凸包, 水平線實現
個人以為對于NOIp來說, USACO Training Chapter4之后的內容的主要價值在于訓練調試能力, DFS+剪枝的題目非常多, 對于訓練暴搜來說是很好的教材. 算法上的話, Chapter4以后的NOIp新內容只有二分法和DFS優化技巧, 但是對于算法組合能力的要求有了進一步提升, 只有基礎非常扎實才能在短時間內AC題目.
不過目前的調試能力仍然有限, 不太復雜的DFS可能需要30 ~ 50min, 較為復雜的DFS可能需要2~3h甚至更長的時間才能調試通過. 對于NOIp來說還是太長了, 簡單DFS應該在20min內調試成功, 復雜一些也應該在1h內調試成功. 這方面還需要進一步訓練.
這次寫USACO Training跳過了4道題, 分別是1.4的某道矩形, 3.4.1的計算幾何, 4.4.2和5.4.5的兩道最小割. 如果日后打算參加GDKOI/GDOI的話, 再彌補網絡流/SCDP/高級數據結構(Trie/線段樹/SBT/樹狀數組)這方面的內容. Chapter5和Chapter6的幾道題還是很難理解題解, 也沒有掌握.
不管怎么說, 我比一年前還是強了很多.
8.13
prime3[DFS + 手動指定搜索順序], 4h, 0.1s左右出解
1.構造所有五位素數, 可以先做一個313以內的素數表, 試除即可. 注意素數末尾不是偶數和0.
*官方題解中枚舉6n-1和6n+1, 同時判斷末位5和0, 從素數7開始枚舉即可, 枚舉量進一步減少
2.手動指定搜索順序, 對于每種情況分別處理, 代碼很長. 對角線優先, 末尾數組優先(顯然素數末尾數字只能是1\3\7\9)
0 21 18 22 5
12 2 13 8 9
16 23 3 24 10
14 7 15 4 11
6 19 17 20 1
大致分為幾種情況:
1)非末位. 邊界條件是 0 <= i <= min{sum(除去已使用位), 9}
2)末位. 邊界條件是i = 1, 3, 7, 9(i <= sum)
3)可計算位(即該行或列或對角線已經填了4個數). sum < 10, 判斷是否為素數, dfs下一層時, sum-下一層已填充位置.
4)對于由情況3)轉移而來的狀態, 需要檢查sum >= 0
*可以把情況1)和2)分別合并, 利用數組指定順序亦可(代碼長度可能變化不大)
*官方題解在這里以行\列\對角線為單位填數, 需要預處理某些位為某些數的素數.
3.對保存的結果進行排序, 利用qsort間接排序即可.
*NOCOW上有人寫15重for循環, 效率比這個低10倍. 但是15重for加上縮進實在不好看.
race3[DFS判斷連通性], 40min
第一問, 去掉每個點后判斷是否能夠到達終點, 利用DFS+vis數組標記即可.
第二問, 第二問是第一問答案的子集. 從原點(標記A不可訪問)和第一問某個點A分別DFS, 如果不存在某點在兩次同時被訪問, 那么滿足題意.
證明: 假設第二問不是第一問的子集, 那么不經過第二問中某個點, 必然可以到達終點, 與題目中對于分割的定義矛盾.
*用Floyd判斷連通性亦可(有向圖傳遞閉包).
shuttle[BFS/數學], 4h
1)BFS做法(n <= 7)
1.利用位運算保存狀態, 1表示'W', 2表示'B'或空格, 附加k表示空格所在位
2.按照字典序轉移狀態(共4種方式), k的方向可以任意理解, 不影響結果. k從0開始, 顯然W只向右, B只向左.
*在草稿紙上寫好狀態轉移的具體代碼(if部分), 注意變量
*讀題, 一開始認為棋盤長度不超過12于是就BFS, 實際上棋盤長度不超過25, 位運算保存狀態會爆內存. 此外輸出限制每行20個數字.
2)數學 by NOCOW
我們憑借極其敏銳的眼光發現這組序列為
4 35 642 1357 642 35 4 (n=3,樣例)
5 46 753 2468 97531 2468 753 46 5 (n=4)
即長度為1,2,3,4,...,n,n+1,n,...,4,3,2,1這樣的2n+1組等差序列
我們討論第1~n+1組序列,這些序列滿足
*公差的絕對值為2
*奇數組為降序列,偶數組為升序列
*對于第i組(1<=i<=n+1),若為奇數組則首項為n+i,偶數組則首項為n-i+2
對于第n+2~2n+1組,可以由對稱性求出.
*官方題解做法類似數學做法, 觀察后得出每次都是空格向右移動2格, 直到不能移動(如果不能向右移動1格的話轉向)或者達到狀態.
*如果輸出字典序最小解, 不妨找規律; 如果輸出所有解, 只能搜索.
milk6[最小割+貪心], 按計劃cheat.
frameup[拓撲排序]
1.構造有向圖. 在讀入圖的同時計算每種矩形的坐標邊界, 然后按照邊界遍歷, 若矩形A上存在B, 則G[A][B] = 1.
2.拓撲排序并輸出. 注意到這里并沒有明顯層次性, 答案可能遠遠超過所得序列長度. 如果標記層次的話, 若圖中指向復雜, 那么會少輸出一部分答案(如測試點7).
*一開始沒有看出來是拓排, 一門心思想怎么DFS.
*打錯變量調了1h, 沒有看出來輸出序列的不同浪費了1h. -> 開始的分析非常重要, 邊寫邊改時間成本太高.
8.14
theme[二分], 1.5h, O(N^3logN)
1.對序列做差, d[i] = A[i+1] - A[i].
2.二分求最大值. 注意l=m+1, 可能存在m為最大值的情況, 所以需要check(l).
3.分別枚舉兩個序列的開端(序列1開端為i, 序列2開端為i+l+1), 如果相同, 繼續比較.
*數組開小, 導致詭異錯誤 -> 讀題!!!, 20min
*注意編寫的程序前分析邊界情況 -> 1h
8.15
調教laptop.
8.16
frameup[拓撲排序], 20min -> 參照 北極南天星 的題解, 意簡言賅
構圖這里不再贅述. 注意到題目要求輸出拓撲序列, 長度一定, 并不像DP中出現明顯層次性.
dfs狀態(當前序列長度), 按照定義做: 每次找入度為0的點u, 然后將于此點存在邊的點v入度-1. 直到所有點都在序列中.
*dfs按照字母升序搜索, 即滿足題目順序要求.
*構圖統計每個點u入度時注意判重, 存在多個點滿足條件.
theme[DP], O(N^2)
f[i][j] = max{f[i+1][j+1]}(A[i+1]-A[i] == A[j+1]-A[j]) + 1
f[i][j]表示以A[i]和A[j]為起點的序列的最大長度, 以j-i為階段, 注意f[i][j]<=j-i. 注意到狀態之間存在依賴性, 各階段間不存在依賴性, 只需保存當前狀態. 不斷更新答案即可.
*不能直接二分, 答案可能小于j-i, 無法寫判定函數 -> 如果對于每個j-i的長度序列單調不下降的話還是可以二分.
8.17
starry[floodfill+模擬], 4h
讀入圖, 對每個點進行floodfill; 如果存在星座, 那么和之前的星座進行判重; 不重復的話, 利用數組st0記錄當前星座. 否則改變標號.
判重首先判斷 長度 和 星體個數(只利用這兩點可以過4個點), 然后直接模擬即可. 在floodfill過程中記錄坐標極值, 然后提取矩形, 和st0中星座比較.
比較需要用到順時針向右轉90度和水平翻轉, 坐標變換分別是(tx-x, y)和(y, tx-x), 可以畫圖觀察.
*一開始沒有考慮如何判重, 想了1h
*沒有考慮同一矩形內存在多個相同星座, 浪費0.5h
fc[凸包(Graham-Scan, 水平序實現)], 1h, 參考黑書
1.以縱坐標為第一關鍵字, 橫坐標為第二關鍵字, 對坐標升序排序.
2.左鏈掃描, 從1到n. 如果棧內存在超過兩個元素, 那么叉積判斷是否>180°(即cross<=0), 滿足的話彈出棧頂元素(利用while). 將當前點加入棧.
3.右鏈掃描, 從n-1到1. 判斷同上.
4.計算周長. 加入初始點和末端點距離, 依次加入棧中元素.
*利用棧表示凸包序列
snail[DFS], 50min
讀入路障坐標(x,y)標記為-1, 其他值為1; DFS過程中檢查下一格是否可行, 若不可行, 如果是邊界或路障可以轉向. 記錄最大值.
*注意當前標記在DFS后清除, 已路過格不可轉向; 障礙縱坐標可能不是一位, 讀入橫坐標后標記該為為'0', 利用sscanf讀入即可.
*想起了去年NOIp第四題, 只有半個小時, 但是寫了floodfill卻調不出來. 心里沒底, 仿佛這是看RP的事情, 今天半個小時的時候發生了同樣的事情, 務必加長算法設計時間.
wissqu[DFS] -> cheat
題目給出了每種牛的個數, 實質上就是帶限制的全排列, 可行性剪枝很多, 代碼大概要100+行.
*不過這題只有一個測試數據, 就是樣例 -_-
8.18
fence3[局部搜索], 1h
標準做法是模擬退火, 或者隨機化搜索. 基本思想是逐漸加精度(官方題解也這么寫).
首先搜索所有整數坐標, 記錄最小距離和當時坐標; 然后在此坐標±1范圍內搜索最小值, 注意增量dx/dy要初始化為0(這時不會更新最小值).
bigbrn[DP], 30min
f[i][j]表示以(i, j)為右下方頂點的正方形的最大邊長
f[i][j] = min(f[i-1][j], f[i][j-1], f[i-1][j-1]) + 1
初始化 f[i][j] = 1; 若(i, j)有障礙, 則f[i][j] = 0, 且該點不進行dp
window[鏈表(字符串實現) + 矩形切割(10行)], 3h
對于操作w/t/b/d, 維護操作序列order, 利用<string>實現. 注意string str(&a[i], &a[j])對應區間為[i, j). 構造小數據調試即可.
對于操作s, 利用矩形切割, 逆序添加操作序列order中矩形(可以同時維護面積). 實現非常簡單, 首先對判斷是否被已有矩形覆蓋(x2 < x1), 然后分成四部分添加未被覆蓋部分.
注意到數據中s操作可以連續, 不妨利用char記錄上一操作, 可以避免一些矩形切割.
*這題想想會覺得頭疼, 但是實現非常簡單(ctypcow實現非常麻煩). 題目并不難, 很明顯下午看題的時候高估了實現難度. 不要給自己制造"難題".
*提交程序中對stdout輸出過多同樣會造成崩潰
8.19
milk4[DFS-ID + DP], 45min
利用DFS-ID枚舉桶(即枚舉r-組合), 然后DP判斷能否裝滿.
f[j] |= f[j-ans[i]](完全背包, 不考慮背包數)
初始化f[0] = 1, 其他為0; f[i]表示可以量出i夸脫的牛奶
*打錯變量(DFS/DP), 應該首先靜態差錯, 而不是上數據.
*有純dp做法, 記錄方案很繁瑣.
schlnet[強連通分量(Tarjan)], 1h
利用鄰接表存儲圖, 利用Tarjan尋找強連通分量(BYVoid有個很好的教程, 主過程19行).
第一問是最小點基, 對求得的所有強連通分量進行縮點, 入度為0的點的個數即為答案.
第二問為min(入度為0的點的個數, 出度為0的點的個數), 需要注意只有一個點的時候需要特判.
[Tarjan]入棧\時間戳 -> 遍歷相鄰邊 -> 未遍歷點遍歷 -> 更新LOW -> LOW是否等于DFN -> 出棧標記SCC
latin[DFS + 數學], 1h
注意第一行題目中已確定, 最后一行和最后一列同樣可以推算, 因而只需枚舉中間(n-1)*(n-2)方格即可. 可以規定第一列升序, 這樣一來計算量只是原來的1/(n-1)!, 可以過六組. 第七組需要cheat, 運行時間大概1min左右(可能常數可以少一些). 標準做法是置換群.(std理解不能)
telecow[網絡流], 按計劃cheat
besty[DFS], 1h, N=7 15s
按照題意進行DFS, 有幾個優化.
1.當點的可選擇方向為2時, 用floodfill判斷圖內未填充點是否可以互相到達, 不能到達則剪枝.
2.如果經過點(1,n), 但填充順序k不滿足題意, 剪枝.
3.內層檢查擴展節點是否符合題意(常數優化).
4.判斷某點到(1,n)距離是否小于n*n-k(加了反而更慢).
*可以輸出狀態觀察歸納不可解狀態的共性, 然后剪枝. -> by dd_engi
*據說可以使用著名的cdqDP..
-> 參考黑書P184, 3h
1.圖中必須沒有孤立區域, 即上文優化1, 這里僅討論■□■的情況.
2.搜索過程中, 除被搜索點和目標點外, 其他點至少有兩個點與之相連. 可以利用數組link[i][j]記錄(i, j)的連通情況.
3.如果移動方向上前一個點未被遍歷, 左邊點未被遍歷, 且左前方點已被遍歷時, 剪枝. 右側同理.
利用剪枝1和2以及前文剪枝2, 在本地N=7需要2.3s, 加上剪枝3在本地需要1.9s, 在USACO卡時AC.
*一個技巧, 可以預先表示行列編號為0和n+1的點已讀, 可以避免對越界的討論.
tour[雙進程DP/費用流/刷表法], 3h, O(N^3)
狀態想對了, f[i][j]表示兩個人分別從起點出發到達城市i\j所經過的城市數.
一開始認為方程是f[i][j] = max{f[i.prev][j], f[i][j.prev], f[i.prev][j.prev]+1}, 覺得不太對, 就去查題解.
北極天南星給出的方程是 f[i][j] = max{f[j][k], f[k][j]} + 1
規定對于任意狀態f[i][j], i > j, 需要注意f[j][k]\f[k][j]必須計算過. 初始化f[1][1] = 1, 其余為0.
*利用strcmp比較字符串即可, memcmp似乎某些情況下會出錯.
*這題和NOIp 08傳紙條的主要區別: 這題是無向圖, 而傳紙條是矩陣. 因而轉移方式不同.
[故事] by BYVoid
這是加拿大IOI93的一道原題。在WC2008中聽吳教授說道,當時參加IOI的選手沒有人了解動態規劃,對這道題束手無策。選手們都用上了最復雜的搜索技巧,有人還寫了雙向搜索,可是都沒有通過。回國后開始研究,最終提出了動態規劃這一算法設計方法,于是動態規劃變成了之后競賽出題的熱點。
8.20
hidden[字符串], 2.2h
最初的做法很猥瑣, 按題意模擬, 記錄最小字典序串的口令. 第8個點TLE了, 大概都要5s左右.
進一步的修正記錄了每個字母每次出現的位置, alpha[i][0]表示出現次數, 類似鄰接表. 只處理字典序最小出現次數非零的字母, 第10個點TLE了.
更進一步, 對于串中若干個連續的該字母, Count[j]表示alpha[i][j]開始, 由該字母組成的最長連續串. 只處理第一個, 一開始沒有考慮環狀, 第11個WA了.
*處理環狀的必要條件是, alpha[i][alpha[i][0]] + 1 == n;
*對于復雜度估計不周, O(N)的常數太大所以掛了, 之后修正沒有考慮環狀情況, 卡了1h.
*秒殺做法包括最小表示法\后綴數組\類KMP\最小后綴, 有空應該學一下著名的KMP算法
cowxor[?]
很容易聯想到最大連續和, 注意到異或運算滿足結合律, 可以利用O(N^2)的時間枚舉每個區間. 但是這題范圍很大, 第6個點就TLE了.
8.21
charrec[DP + 統計 + 記錄方案], 2.5h
注意到題目對于最優解的定義是損壞數據最少, 因而可以考慮DP.
f[i] = min{f[i + 19] + C[i][19], f[i + 20] + C[i][20], f[i + 21] + C[i][21]}
prev[i] = i + 19\20\21
prevk[i] = K(C[i][_]對應的字符)
f[i]表示從第i行到第n行的最少損壞(不同位數). C[i][j]表示從第i行開始連續匹配j行的最小損壞(不同位數), 這里需要返回所對應的字符, 實現時可以利用記憶化.
對于f數組, (n - 19, n]需要初始化為+∞
*可以利用d[i][j][k]表示第i個字符第j行和輸入數據第k行不同位數
*需要注意數組大小, 以及題目中寫出的font.in和給出的font.in不同.
picture[離散化+掃描線? / 離散化+線段樹 / 離散化], 1h
1.無需離散化, 直接染色, 可以過8組數據. -> But, 第7組莫名奇妙的掛了.
[統計] 對于兩線段交點, (G[i-1][j]^G[i+1][j]) && (G[i][j-1]^G[i][j+1])(即(i,j)左右\上下相鄰點顏色不同).
對于一般點, 水平方向上, 可以(G[i][j-1]^G[i][j+1]) && G[i-1][j] && G[i+1][j]. 豎直方向同理.
2.掃描線 + 離散化 by BYVoid
對于水平和豎直方向分別處理, 下面僅討論水平情況, 豎直同理.
(1)對于每個矩形, 將其在水平方向上的邊按照縱坐標的大小, 分為始邊和終邊.
(2)按照縱坐標升序掃描, 相同情況下始邊優先(因為邊重合的時不算入周長). -> 快排即可
(3)如果遇到始邊, ++ level[i]; 如果遇到終邊, -- level[i]; 如果出現level[i]從0到1的情況, ++ ans; 從1到0的情況相同, 不必考慮.
-> 對于這一題直接掃描每個點即可, 如果數據范圍進一步擴大的話, 不妨使用線段切割.
答案即為ans*2;
*很像KOI10 Day2的某題, 當時貌似我直接模擬, 然后數組爆了. 其實用sizeof算一下內存即可.
*突然發現神牛的構造能力都很強. 比如說BYVoid的做法, 個人以為最重要的是level數組的構造.
*如果自己寫計時函數并提交的話, 會發現USACO函數顯示的時間比USACO給出的時間少得多, 原因不知.
*很奇葩的是, 以前一直用PROG, 然后USACO這次突然提示使用PROB -> 什么狀況
twofive[Contor展開], ?h, 全仿std.
1.暴搜 -> 理論6組, 實際4組 -_-
2.類似kimbits, 可以使用康托展開(比較直觀的解說參見白書第10章).
關鍵在于對于一個給定的串, 如何知道小于其前n位的串的方案數. 定義f[a][b][c][d][e]表示方案數. 對于每個字母ch, 如用過, 那么繼續ch+1. 否則枚舉已填位后所有位, 累計方案值.
(數學描述)
若ch未被使用, f[a][b][c][d][e] = f[a+1][b][c][d][e] + f[a][b+1][c][d][e] + f[a][b][c+1][d][e] + f[a][b][c][d+1][e] + f[a][b][c][d][e+1];
初始化f[5][5][5][5][5] = 1.
*[數組開小]
> Run 1: Execution error: Your program had this runtime error:
Illegal file open (/dev/tty). The program ran for 0.000 CPU
seconds before the error.
rectbarn[DP], 1.5h
1.暴力, O(R^2*C^2*P), 可以過3個點
2.參考WC03王知昆論文, 非常好理解
懸線:上端點覆蓋了一個障礙點或達到整個矩形上端的有效豎線. 顯然對于每個懸線擴展得到矩形一定包含答案. 我們可以利用O(RC)的時間得到所有懸線, 因而問題的關鍵在于利用O(1)的時間處理懸線. 這里可以使用DP.
H[i][j] = H[i-1][j] + 1 (G[i][j]未損壞)
0 (G[i][j]損壞)
L[i][j] = min{l[j], L[i-1][j]} (G[i][j]未損壞)
∞ (G[i][j]未損壞)
R[i][j] = min{r[j], R[i-1][j]} (G[i][j]未損壞)
∞ (G[i][j]未損壞)
max{H[i][j]*(L[i][j] + R[i][j] - 1)}即為答案
*北極天南星 和 論文 似乎都有一個地方把 min 打成了 max.
*i和j不能隨意交換, 需要注意所對應的下標范圍.(#3 WA)
vans[DP / SCDP], 1h
奇葩的遞推式, f[n] = 2*(f[n-1] + f[n-2] - f[n-3]) + f[n-4] (n >= 5)
初始化, f[1] = 0, f[2] = 2, f[3] = 4, f[4] = 5; f[50]之后需要高精度.
*推導方法待研究.
*求哈密頓回路個數, 可以使用基于連通性的SC.
cowxor[枚舉 + 優化], 1h, 全仿std
大概在暑假之前, 我從來沒有想過能在暑假刷完Training.
去年初賽后, 刷了幾道Contest Bronze的水題, 想要重啟Training, 但是在寫了一道完全背包之后, 水平所限, 無可奈何的放棄了.
從高一開學, 到NOIp后, 只寫了38k的程序. 今年5月, 寫了24k. 當時的狀態, 找不著北, 還要面對文化課的挑戰. 而當時的計劃又比較凌亂, 不管是訓練還是比賽. 按照我當時的能力, 應該在9月初開始, 一直到11月一直寫DP. 期間可以穿插一些圖論和搜索的復習, 11月可以學會寫一些簡單的暴搜(這個當時做到了), 背一下裸的圖論代碼(當時也做到了, 但是沒有上機, 應該找裸的算法題不斷提交默寫代碼). 不過確定校內上機環境已然是開學第三周了, 還有軍訓一周. 時間本不多, 加上中考的失落, 以及高一的迷茫.
NOIp后曾一度拿到lrj老師推薦的題目, 大概是兩次吧. 不過UVa太猥了, 只有AC與否, 而且輸出格式紛繁, 實在是初學者的噩夢. 當時水平有限, 或者說更確切的是自信有限. 不相信自己寫得出這樣的題目. 首先要相信自己能寫某一題, 然后就會寫了. 后面無可奈何的不了了之. 有點印象的僅僅是一道樹(模擬), 和spfa記錄方案.
寒假面對實現能力痛定思痛, 重寫Chapter3. 大概一直到3月份才完成, 除了msquare和comelot. 同時一直寫在線的Contest, March終于進了Silver, 而且寫的也不錯. 三個月寫了120k的代碼, 效果不錯, 之后就是歷時一個余月的”迎大運”斷網活動. 應該說這三個月實現能力有了初步提升.
4月份幾乎一個月耗在了Chapter4上, 可是僅僅A了兩道最大流和一道最小環. 嘗試了幾道題, 但是無果而終. 寫了幾種msquare, 這應該是第二次研究Contor展開. 這個月網絡完全癱瘓, 第一周還腹痛如絞. 進度極慢.
5月初面對段考的結束, 以及4月份的奇慢進度, 和dy學長談了幾乎一晚上. 之后就按照學長建議開始寫DP, 從tyvj開始. 一開始面對LIS和背包很輕松的解決了, 但是其他類型壓力很大, 經常卡幾天然后某一天突然A兩三題. 之后學了一些東西, 看了jec推薦的動歸八講 和 一套網上的題解. 而后得到了Ylen的建議, 開始考慮看題解和思考的平衡點. 期間還準備了LGOI的初賽, 結果較挫, 題目比較莫名其妙(完善最后一題), 大概前五吧. 重做去年初賽, 復習了空間向量, 突然發現去年第四題理解錯題意, 其實就是一個查表找最小字典序. 去年卻認為必須模擬, 還畫了二三十幅圖.
5月份的收獲主要是方向性的, DP和題解, 去年的思維誤區. 盡管5月份訂的目標是50道, 但是期末考試前大概只寫了20道, 應該還是盲目思考太多. 思考和學習之間需要一個平衡點, 隨著時間的推移, 會越來越偏向思考. 但是過早的傾向于思考會加大時間成本.
6月份又開始準備段考, 盡管結果很挫. LGOI的復賽正好和上課時間沖突了, 也沒心情去, 不了了之. 當時的能力大概會300的算法, 不知道實現如何. 現在會400的算法, 不妨一試. 6月份的記憶似乎就剩下糾結前面某安保負責人的網線 和 生物王后雄了. 很難得的AK了王后雄前四章.
暑假頹了半個月, 看小說, 看電視劇, 看電影, 準備訂精華的課, 稍微動了動作業.
半個月開始正式訓練, A題速度快了很多. 也學了很多東西, 比如樹形DP(目前只會兒子兄弟表示法), 同時鑒于tyvj的難度, 開始注意一題多解. 大概10天之后開始寫朱全民的<動態規劃典型例題>, 寫了22題, 除了兩道SC. 大概總共用了3周時間完成上述工作, 52k代碼. 進一步鞏固了樹形DP, 開始接觸SC. 信心大增.
于是在個人膨脹的大背景下, Training被重新提上日程. 用了兩天的時間學習和復習暴搜, 重寫了n皇后問題(位運算無能). 用了一周左右的時間寫完了Chapter4, 參考了一些題解, 除了fence8沒怎么參考別人的AC程序. 因為入手新laptop的緣故, 停兩天, 然后開始Chapter5, 學習凸包, 在window卡了半天. 第二天發現實現非常之簡單, 完全是去年寫矩形切割留下的心理陰影, 主程序才10line, Tarjan都要20line. 之后最惡心的題目應該是twofive, 完全的實現無能, 照著標程打了一遍才部分理解. Chapter6的題目幾乎都會寫暴力, 但是AC算法確實想不到, 而且居然涉及到多篇WC論文. 不管怎么樣, 總算過了. 實現能力越來越強, 只要明白了算法, 可以在3h內調出95%的程序. 但是由于題目難度, 很多題目做不到一題多解. 這半個月可能是搞OI以來最充實的半個月, 寫了87k的代碼.
如果日后準備KOI甚至更進一步的話, 不妨重寫一遍Training后面的題目. 不同的是注重高級數據結構. 這次比較注重實現能力\調試能力和暴搜的訓練, 較為輕視高級數據結構和高級算法(比如兩道最小割都被無視了).
這一年以來, 最重要的轉折點應該是5月, 其次是1月, 然后是7月. 從5月開始, 量變逐漸產生質變.
接下來的任務是NOIp 2011, 目標應該是300左右, 雖然大部分年份的題目估計在3h內單題AC. 但是考慮到整體時間, 應該做到45min內出第一題(水題情況), 并且讀完全卷, 制定計劃. 后三題按照難度大致完成兩題, 必須完成每題的暴力部分, 然后寫對拍.
知識盲點還是有一些, 比如Contor展開, Hash, KMP, 最短路算法普遍很陌生, 并查集的應用, 雙向廣搜, Tarjan等. 如果時間更多的話, 還有各種數論和遞推, 線段樹, 樹狀數組, 最大流. 實現盲點主要是對拍和暴力, 也許應該先寫暴力, 然后寫AC算法(或者更強的部分算法), 寫makedata, 對拍. 寫暴力和makedata的時間應該可以控制在10min內, 要多訓練.
題目的話, 歷年題目還有很多沒寫, POJ的DP分類, 各種模擬賽, Ural. 9月份以前兩者為主, 主要是練習算法, 增強熟練程度, 寫一些有難度的算法題. 明天先寫完Training最后兩題的題解, 然后復習一下Tarjan. 剩下幾天以整理暑假題目為主, 每天大概A1~2道題, 從LGOI和歷年開始. 至少在NOIp范圍內, 沒什么題目不能做.
和一年前相比, 我更多的知道了我應該做什么, 或者說, 更相信自己能做什么. 這應該是一年來最大的突破.
8.6
poj 1731[全排列生成]
參照白書上的做法, 關鍵在于
(1)遞歸的邊界條件cur == n
(2)遞歸調用的條件c1(在生成序列中出現次數) < c2(在原序列中出現次數)
(3)!i || P[i-1] != P[i]判重
*需要注意, qsort可以直接對char排序
RQNOJ四周年模擬賽 -> 據說是NOIp難度 -> 審題壓力很大
[七顆果子]給出一個數開七次方
30%的做法是直接開七次方
AC算法, 注意到輸入不超過777位, 所以答案不超過[lg(10^(777/7))]+1 = 112位, 二分次數不超過log{2}{10^112} = 372.很明顯對于每一個輸入我們可以得到答案的位數, 先利用位數確定范圍, 然后壓位高精乘.
[七夕星夜]
此題無思路..只會模擬. 猜測標準算法是DP+優化
[七色彩虹]
將起點和終點各看做一朵云彩, 題目可以看作規定起點和終點的DAG最長路問題. 方程f(i, j) = min(f(k, j-1) + w(i, k)), 時間復雜度是O(N^7).由于每個點經過不止一次, 不能利用vis.想不到什么優化.
[七夕的禮物]
模擬或者利用公式[usaco friday涉及的蔡勒公式]
[估計]理論得分190, 實際上120左右, 還不一定調的出來. 去年做的話, 應該能90左右.
8.7
checker[DFS]
思路和昨天的生成全排列如出一轍, 可行性剪枝都在擴展節點的時候, 不同的是此題利用vis避免了循環檢查.
*把vis數組利用位運算實現, 速度提升不大, 從0.73s到0.7s而已.
*M67給出的位運算做法只需要0.135s, 測試后發現兩程序遞歸次數一樣. 主要差別應該是在循環擴展節點.
*vis下標打反一次, 沒有看對稱性剪枝.
*發現一個驚人事實, USACO服務器計算能力加強了, NOCOW上據說0.9X的程序, 現在提交0.6X.
poj 1049[DFS], 1h, 3WA
直接套用生成全排列, 不同的是遞歸邊界條件不是l而是c, 擴展節點注意滿足A[i-1] < A[i]
*萬般無奈找std對拍, 復習了隨機數和bat的寫法, 再次提示, 注意靜態查錯!!!
fence8
全排列生成+可行性剪枝, 時間復雜度O(K!), 只過了一個測試點 -_-
poj 3050[DFS], 50min
學習DFS-ID, 突然發現我昨天寫的事實上就是DFS-ID. 不同的是, DFS-ID將深度逐漸遞增, 進行多次搜索. 也就是說會存在一些冗余, 但是鑒于解答樹的節點主要集中在最后兩層, 所以前面的狀態量并不大. 事實上DFS-ID結合了BFS處理距離節點最近的問題, 以及DFS的空間復雜度優勢.
*調試了20min, 原因是因為對狀態編碼的一個循環寫錯了. -> 如果涉及多組易混變量的話, 一定要重點檢查.
*Hash表的MaxState如何確定?
[Training關于BFS\DFS\DFS-ID的介紹]http://www.oiers.cn/usaco%20training/11-401.asp.htm
8.8
fence8[高仿dd_engi牛的程序]
枚舉每一個board可以切成那些rail, 分析各種剪枝的動機.
優化:
1.排序后計算Σrail[1..i]>Σboard[1..N]的R=i的最小值 => 縮小R的可能性
2.二分[如何處理邊界???] => 減少DFS-ID的枚舉量
*關于邊界處理的動機 by gXX
因為唯一的問題就是出現在[i, i + 1]這種區間, 你總是要調整mider的取法, 使得每次至少能夠刪掉一個元素。
(1)如果mider成功調整為[left, mider]否則是[mider + 1, right]
那么你需要寫一個mider = (left + right + 1) / 2;
(2)如果mider成功調整為[mider, right]否則是[left, mider - 1]
那你就要寫mider = (left + right) / 2
(3)在區間長度小于2的時候進行特判
3. rail[i] == rail[i-1] -> 這一個能切出來, 下一個直接試能不能切一個同樣的
4. board[i] == board[i-1] -> 相等的情況下, 這個能切出來, 下一個顯然也能切出來
[關于剪枝3和4]
如果有兩個欄桿的長度相同,那么這兩個欄桿從哪兩塊木板或者從一塊木板中切出對最終結果是沒有影響的,所以在遍歷木板的時候,可以按照木板的順序進行切割。比如規定一種次序,編號靠前的木板優先切割相同欄桿的第一個,以此類推,那么當rail[i]==rail[i+1]時,現在用第k塊木板切出欄桿i,則欄桿i+1就從第k塊或者更后面的木板進行切割,而不必再從頭開始,因為i+1在i前面的情況已經搜索過了,其實將i+1與i的位置互換一下就是了;同理,如果兩個木板相同,那么也規定靠前的優先切割欄桿;
[剪枝動機] by wh
這個剪枝主要是充分利用數據性質進行不等式控制,和生日蛋糕有點類似之處。所以說對于“切割”類的搜索題,通常他的“剩余部分”往往隱含著一些限制,可以作為剪枝的突破口
另外這個剪枝有個使用前提,就是要先轉化成判定性問題,因此這也是我們必須id-dfs的原因
rqnoj 350[查找第k大]
這題對于類似快排來說數據比較強, Hoare劃分法的時間常數比Lomuto劃分法時間常數少一些, 平均復雜度O(N), 最差的話O(N^2). 真正意義上的O(N)算法似乎是非常麻煩的分治. 在本題中復雜度為O(MN). 使用平衡樹的話可以做到O(MlogN).
*需要注意的是, 算法中存在swap操作, 故而如果求區間最值的話需要重新賦值
*Hoare的話實際上就是左右開弓..
*Lomuto的話實際上就是折騰序列最后一個數, 所以會多了很多不必要的swap
*這個隨手寫的例子不錯
7 7
5 1 3 2 6 8 7
tyvj 1001[查找第k大/小 + 素數判斷]
練習Lomuto劃分法查找第k大
*查找第k大算法O(N)較直接排序O(NlogN)的差別不大, 而且更大的數據范圍需要更高級的數據結構, 總的來說用處不大.
buylow[LIS+高精], 3h+1.5h.
方程考慮不周1h, 調試高精2h, 理解算法1h.
對于第二問, 注意到對于f[i] = f[j] + 1(j < i), 存在A[j] <= A[i](因為題目要求最長不下降子序列). 因而可以記錄prev[i]表示f[i]的上一個f[j]的值, 注意f[i] > f[j]+1和f[i] == f[j+1]都要更新.然后會在第5個點掛掉.
即使是相同的A[j], 它們所對應的g[j]不同, 按照題意應取最大的. 故而prev[i]需要記錄上一個j的下標.
f[i] = max(f[j]+1);
prev[i] = j(f[i] >= f[j]+1)
g[i] = Σmax(g[prev[i]]) (每個不同的A[j]對應的最大g[j]) -> 這里需要不斷更新, 因而需要高精減
為了簡化, 在序列末尾增加元素0, 故而f[n], g[n]即為答案.
*對于方程2, 可以用next[i]記錄與A[i]相同且最近的值的下標, 計算方案時若next[i] && next[i]<i直接跳過. by BYVoid
->同樣的A[i], 最后一個的g[i]最大. 假設A[j]==A[i], 存在g[j]<=g[i], 若區間(j, i)不存在A[k]>A[i]等號不成立.
*對于雙目運算符, 函數()后要加const, 函數參數要加const和&, 注意利用初始化結合題目要求.
*第一版程序+=函數出現前導零
*對于每一道相似的題目, 要著重思考不同點, 在紙上得到正確的方程. 否則會在調試過程中浪費兩倍的時間.
*靜態查錯可以避免浪費更多的調試時間, 要分析每個函數每條語句的動機, 以及實現情況. 不要盲目調數據.
8.9
fence6[DFS], 2h, 數據弱
*這題充分暴露了對題意分析不明, 貿然上手的后果. 此題應在40min內解決, 20min左右設計DFS, 剩余時間調試.
(1)標號. 利用map數組存儲標號, 然后將下面的讀入下標全部用map映射. -> 讀題時應該完成
(2)DFS. 枚舉每個點, 向右邊DFS, 除了出發點不能再次加入. 但是題目中給出的left和right是亂序的, 也就是說必須記錄prev, 然后從不含prev的一側繼續遍歷. 所以DFS的狀態為(起始邊, 正在遍歷邊, 上次遍歷邊, 已遍歷邊權), 利用循環檢查確定遍歷方向.
*以前寫Chapter2時就是這樣, 題目在自己的能力范圍內, 但是不注意題目的分析, 浪費大量時間.
cryptcow[算法1, 實現無能], 8h
讀入原串后記錄C\O\W的個數及位置, 個數不同則直接無解, 同時處理前綴和后綴(第一個C之前的和最后一個W之后的),然后深搜.
深搜過程(層數直接通過(原串-目標串)/3確定)中出現的新字符串用map記錄下標, memcpy完成下標的復制和恢復, 利用vis判重(對于每個C\O\W).
其中比較麻煩的是C\O\W在變換后位置的更新和恢復, 花費了很多時間調試. 就算在紙上先算好下標的變換值, 還是很容易因為多個函數同時改變導致錯誤. 第二天上午最終過了3個數據點, 其余提示崩潰, 調試無能.
*了解一些細節:std關鍵字會引起沖突; memcpy(,,字節數), 一般為sizeof(類型)*數量
*比較奇怪的是利用fgets讀入第一個數據會自動加回車.
8.10
ctyptcow[算法2], 5h
通過學習<string>的一些特性(《C++ Primer Plus》記錄的很詳細), 50min寫完了不加任何優化的爆搜, 過了5個點.
1.得到s串的子串[i,j]: string newname(&s[i], &s[j]) -> 類似這樣的函數還有別的一些神奇的寫法
2.通過+\+=完成連接; .size()/.length()計算長度
3.getline(file, value) -> 有value的長度限制讀入長度, 不必顯式制定.
接下來是實現各種優化:
1.改變搜索順序, 先順序搜O, 然后順序搜C, 最后逆序搜W(逐層if)
2.判斷變換后的串COW間的子串是否為原串的子串, 可以記錄原串每種字符出現的位置, 不是原串子串的話, 剪枝
3.判斷變換后的串第一個C, 最后一個W的位置, 若C>W或從兩端沒有找到C/W但是找到O, 剪枝
4.利用ELF Hash. 嚴格意義上這步算騙分, 選取質數131071時恰好沒有沖突. 但是嚴格意義上的Hash需要判重
5.找到第一個C和最后一個W, 確定DFS字符范圍 -> 這個用了反而TLE
*對于涉及到數組的問題, 不一定要按照原來改變 -> 恢復的模式, 應該設法避免“恢復”過程.
*關于Hash, 參見WC2007李羽修《Hash函數的設計優化》, 很驚悚的發現我居然用直接取余A了.
*最后一個測試點卡著過的.
8.11
job[貪心(對稱性) + 二分], 1h
首先要注意到這題是多機器并行工作, 所以不能使用DP, 可以貪心. 事實上如果從1到max{A[i]}*n逐個枚舉t, 就可以得到最小的t. 這提示我們可以使用二分, 判定函數為Σt/A[i](1<=i<=n).
對于兩種操作A、B, 要分別計算最短時間和方案(要保證A[]\B[]序列升序), 然后利用對稱性找最大值.
*計算方案利用二重循環, 1..t為外層i, 1..na\nb為內層j, 未生產工件數為rest, 如果rest>0&&i|j, 那么ta\tb[n-(--rest)] = i;
*對于二分, 目前掌握兩種寫法. 一是lrj白書l>=r;check(m);l=m+1;r=m, 二是dd_engi在fence8中的l+1=r;check(m-1);l=n;r=m;
stall4[二分圖最大匹配 -> 匈牙利算法]
[二分圖最大匹配問題匈牙利算法]http://www.matrix67.com/blog/archives/39
[匈牙利算法]http://www.byvoid.com/blog/hungary/
[二分圖最大匹配的K?nig定理及其證明]http://www.matrix67.com/blog/archives/116
第三項和這題關系不大.
[匈牙利算法] by M67
從二分圖中找出一條路徑來,讓路徑的起點和終點都是還沒有匹配過的點,并且路徑經過的連線是一條沒被匹配、一條已經匹配過,再下一條又沒匹配這樣交替地出現。找到這樣的路徑后,顯然路徑里沒被匹配的連線比已經匹配了的連線多一條,于是修改匹配圖,把路徑里所有匹配過的連線去掉匹配關系,把沒有匹配的連線變成匹配的,這樣匹配數就比原來多1個。不斷執行上述操作,直到找不到這樣的路徑為止。
*可以從每個點開始找增廣路, 最初的覆蓋是空集, 第一個點過后就有了一條邊, 之后邊數會逐漸增多, 直到不存在交錯路.
*似乎這題應該是無向圖, 但是寫有向圖也不影響結果.
tyvj 1035[二分圖最大匹配 -> 棋盤覆蓋], 1h
對棋盤進行染色, 標記不能通過的點, 然后對于每個點(x,y), 分別和(x-1,y) (x,y-1) (x+1,y) (x,y+1)建立邊(另一個點必須可以通過). 這里可以把二維坐標一維化, 之后這屆套用匈牙利算法即可.
*注意輸入中的坐標從1開始, 而實現中從0開始
*注意擴展邊的時候, 新點同樣可以訪問 -> 卡了30min
poj 1422[二分圖最大匹配 -> 最小路徑覆蓋], 20min, 1Y
[轉載]在一個有向圖中,路徑覆蓋就是在圖中找一些路徑,使之覆蓋了圖中的所有頂點,且任何一個頂點有且只有一條路徑與之關聯;(如果把這些路徑中的每條路徑從它的起始點走到它的終點,那么恰好可以經過圖中的每個頂點一次且僅一次)
將圖中的每個點拆為出點和入點, 將原來的邊轉化為出點和入點間的有向邊, 利用匈牙利算法可以得到最大匹配數p. 顯然二分圖中每個匹配的出點都存在后繼, 故而這些點不可能是DAG的重點, 因而答案為n-p.
[這個寫得比較好]http://www.cnblogs.com/jackiesteed/articles/2043934.html
向gXX神牛請教如何對拍.
cowcycle[DFS], 2h
同時枚舉F-組合和R-組合, 數據較弱, 加上可行性剪枝就A了.
1.搜索順序
注意到題目要求答案字典序最小, 故而可以逆序枚舉避免對字典序的判斷.(也可以順序, 然后找一個比較強的條件, 在完成找到答案后退出, 效率估計逆序的幾倍.)
2.狀態設置
dfs(當前F kf, 當前R kr, 剩余F數 nf, 剩余R數 nr).
3.終止條件
nf = nr = 0 || kf + 1 < F1 || kr + 1 < R1.
對于nf = nr = 0, 進行可行性剪枝(判斷傳動比率倍數) -> 注意這里是int
4.狀態轉移
非常麻煩, nf/nr為零的情況需要單獨處理, 4種轉移方式, 但是有8種情況.
5.優化
1)考慮到生成的待排序序列與排序后很接近, 而且數據量極小, 于是用冒泡代替快排
2)求平均值時, 先累加, 最后除; 求方差時直接忽略除, 不影響結果
這樣可以把最后一個點從1.9s優化到0.6s, 很有效的控制常數.
*所有同時涉及double和int的語句, 必須進行強制類型轉換
*memcpy(ansF+1, f+1, sizeof(int)*F)必須這樣寫, 如果將第三部分寫作sizeof(f)或sizeof(f+1)導致錯誤 -> 原因不明
*對于這類比較復雜的搜索, 應該現在紙上寫出大致模型, 并試圖找出一些問題. 直接寫程序很容易寫錯變量名, 或者出現設計問題. 這樣應該能夠大幅減少調試時間.
8.12
lgame[枚舉 + 優化], 2h
1.讀入目標串并記錄分值.
2.讀入字典, 如果字典中串的長度小于等于4, 且目標串長度大于7, 記錄該串. 需要注意該串的每一個字母出現次數不應超過目標串出現字數.
3.如果該串分數大于等于目前記錄最大分數, 則更新答案. 比較奇怪的是這里memcpy直接用sizeof(dic[t])不影響結果, 可能是字符串的原因.
4.對記錄子串進行枚舉, 并更新答案. 這里如果利用字符數組的話, 要注意計算連接處的坐標(不妨利用for, memcpy容易出錯)
*對于字符串可以利用qsort間接排序, cmp函數先逐位比較, 然后比較長度.
*注意到題目中每個單詞不超過7個字母, 但是輸出中可能包含空格! 如果內存過小或不指定'\0'的話, 會繼續輸出后面的字符串.
*對于這類寫代碼都需要0.5h的題目, 需要思考如何減少調試時間, 現在調試時間是思考算法和寫代碼時間的兩倍.
USER: Yupan Liu [liuyupa1]
TASK: cryptcow
LANG: C++
Compiling...
Compile: OK
Executing...
Test 1: TEST OK [0.000 secs, 3240 KB]
Test 2: TEST OK [0.000 secs, 3240 KB]
Test 3: TEST OK [0.000 secs, 3240 KB]
Test 4: TEST OK [0.000 secs, 3240 KB]
Test 5: TEST OK [0.000 secs, 3240 KB]
Test 6: TEST OK [1.377 secs, 3240 KB]
Test 7: TEST OK [0.054 secs, 3240 KB]
Test 8: TEST OK [1.026 secs, 3240 KB]
Test 9: TEST OK [1.782 secs, 3240 KB]
Test 10: TEST OK [1.971 secs, 3240 KB]
All tests OK.
Your program ('cryptcow') produced all correct answers! This is your
submission #37 for this problem. Congratulations!
僅此記錄AC的喜悅.
7.26
最長公共子序列lcs, O(N^2)
f[i][j] = max{f[i-1][j], f[i][j-1], f[i-1][j-1]+1(if A_i==B_j)}
初始化f[_][0] = f[0][_] = 0
7.27
編輯距離edit, O(N^2)
f[i][j] = min(f[i][j-1] + 1, f[i-1][j] + 1, f[i-1][j-1] + !(A_i==A_j))
初始化f[i][0] = f[0][i] = i
*參考[這里]http://en.wikipedia.org/wiki/Levenshtein_distance
*狀態轉移過程中, 充分不一定最優, 必要才是最優; 事實上邊界條件總有其具體意義
*[相關]http://www.matrix67.com/blog/archives/333
最短回文串palindrome[poj 1159], O(N^2)
1.套用lcs, O(N^2), 60
f[i][j] = max{f[i-1][j], f[i][j-1], f[i-1][j-1]+1(if A_i==B_j)}
初始化f[_][0] = f[0][_] = 0, n - f[n][n]即為答案
*利用滾動數組優化, 空間復雜度O(N), 80
*關鍵語句k = 1 - k, 注意在內層循環外
2.套用edit, O(N^2), 30
f[i][j] = min(f[i][j-1] + 1, f[i-1][j] + 1, f[i-1][j-1] + 2*!(A_i==A_j))、
初始化f[i][0] = f[0][i] = i, f[n][n]/2即為答案
3.O(N^2), 30
f[i,j]表示將Ai..Aj變為回文串的最小代價,則
f[i][j] = f[i+1][j-1] (若Ai=Aj)
min(f[i+1][j],f[i][j-1])+1 (若Ai<>Aj)
4.利用位運算優化
http://www.csse.monash.edu.au/~lloyd/tildeStrings/Alignment/86.IPL.html
硬幣找零coin[完全背包], O(N^2)
f[j] = min(f[j], f[j-c[i]]+1)
初始化f[0] = 0, f[1..T] = INT_MAX
*注意下標非零 和 INT_MAX的溢出
7.28
導彈攔截missile[LIS + 二分], O(NlogN)
(1)二分查找O(logn)
f[i] = max(f[j] + 1) (j < i)
d[i] = min(f[k]) (f[k] == i)
易知d[i]單調, 因而可以利用二分查找降低復雜度, i最大值即LIS長度為t, 那么
i) f[i-1] < k <= f[i] (1 <= i <= t)
ii) 若k > 任意f[], 則f[t+1] = k;
iii) 若!k, 則f[1] = k;
*例子參見[這里]http://www.matrix67.com/blog/archives/112
[代碼實現]
//情況ii和iii需要單獨處理
x = 1; y = t;
while (x <= y){
m = (x + y)/2;
if (f[m-1] < k && k <= f[m]) break;//對于最長上升子序列和最長不下降子序列判定條件不明???
//if (f[m-1] < k && k <= f[m]) return m;
else if (k > f[m]) x = m + 1;
else y = m - 1;
//return x;
}
*利用注釋, 可以避免對情況ii的單獨處理LIS的方案, 記錄方案需要使用pre數組, 范例不知???
*需要注意的是, f數組給出的并非是一個
(2)最少鏈劃分 = 最長反鏈長度(關于偏序集, 參見《組合數學》P73)
[Dilworth定理]令(X,≤)是一個有限偏序集,并令m是反鏈的最大的大小。則X可以被劃分成m個但不能再少的鏈。
最長不下降子序列lis[LIS + 二分], O(NlogN)
對[1..k-1][k+1..n]兩個序列分別進行一次LIS即可.
*問題的關鍵之處在于第一次理解不徹底和浮躁, 以及對于困難程度的不合理估計.
7.29
加分二叉樹tree[區間 + 記錄方案], O(N^3), 20min
f[i][j] = max(f[i][k-1] * f[k+1][j] + A[k]) (i <= k <= j)
初始化f[i][i] = A[i], f[i+1][i] = 1
[記錄方案]pa[i][j] = k, 遞歸即可, [邊界]pa[i][j] == i 或者 j, 以及i == j的情況
整數劃分separate[區間 + 記錄方案], O(N^3), 2h
f[k][i]表示序列1..i分成k段的最大值
f[k][i] = max(f[k-1][j-1] * A[j][i])
pa[k][i] = j
初始化f[1][_] = A[1][_], 其他f[][] = -1
*注意等號情況同樣需要更新
if (f[K][i] <= f[K-1][k-1] * A[k][i])
f[K][i] = f[K-1][k-1] * A[k][i],
pa[K][i] = k; //記錄方案
*將[pa[k][i], i]加入答案, 遞歸[1, pa[k][i]-1], [邊界] k == 0
*在Win下使用long long占位符為"%I64d", 在Linux下占位符為"%lld", 考試中利用<fstream>避開占位符問題
凸多邊形的三角剖分division[區間]
f[i][j] = max{f[i][k] + f[k][j] + w(i, j, k)} (i < k < j)
初始化1<=i-j<=2的f只為0, 其他為-1
*表達式中同時出現long long和int的話, 會自動轉換為int
*只過了6個點, 原因不知 -> 數據錯誤, 最后4個點output一樣
*各種神牛們普遍指出沒有考慮i>j的情況 -> 怎么寫???
機器分配machine[區間], O(N^3)
f[i][j] = max(f[i-1][k] + A[i][j-k]) (0 <= k <= j)
初始化f[][] = 0, f[i][j] = max(f[i][j], A[i][j])
*注意讀題"第i個公司分配j臺機器的盈利", 不是分配第j臺機器的盈利
裝箱問題box[分組背包], O(N^2), 30min
f[i][j] = max{f[i-1][j], f[i-1][j-c[i][k]] + w[i][k]}
初始化f[][] = 0
*讀題時注意變量的對應關系
*注意本題中背包不一定要裝滿
7.31
最長前綴prefix[判斷性dp], O(kN), 70min
f[i] |= f[i - len[j]] & check(i - len[j] + 1, j) (1 <= i,j <= n)
初始化f[] = 0, check(x,y)表示主串[x,x+len[y]-1]和前綴y是否相同
*弄錯j和len[j], 注意方程的字母指代, 以及實現中的字符指針位置, 注意靜態查錯[30min]
*[8.4優化]把check函數直接寫在循環中, 如果f[i] == 1直接break -> 依舊超時三個點
*[8.5優化]i的上限為min(n, ans + 20), 更新f[i]的時候記錄ans即可 -> AC
8.1
關鍵子工程project[DAG最長路], 70min
f[i] = max(f[j] + w[i]) (G[j][i] == 1)
初始化f[i] = w[i]
記錄方案, 利用f[i] == w[i] + f[j] (G[j][i] == 1)
*利用定義求拓撲排序, 輸出方案可以利用隊列將遞歸轉化為迭代, 無解情況用flag標記(inq數組表示是否在隊列中)
*在紙上寫出關鍵部分的代碼, 兩倍行距(比如遞推或者記憶化函數, 輸出方案的函數)
8.2
三角蛋糕trigon[坐標dp], 130min
[做法1](需保留空格)
f_[i][j]表示以(i, j)為頂點的Rt△的最大邊長
對于倒三角形, 自右向左 f1[i][j] = min(f1[i+1][j], f1[i][j+1]) + 1
自左向右 f2[i][j] = min(f2[i+1][j], f2[i][j-1]) + 1
對于正三角形, 自右向左 f1[i][j] = min(f1[i-1][j], f1[i][j+1]) + 1
自左向右 f2[i][j] = min(f2[i-1][j], f2[i][j-1]) + 1
初始化, f[][] = 0(A[][] = '#'), f[][] = 1(A[][] = '-'); min(f1[i][j], f2[i][j])^2的最大值即為答案
[做法2](不需保留空格)
f[i][j]表示以(i, j)為頂點的△的最大高度
對于倒三角形, f[i][j] = min(f[i-1][j], f[i-1][j+1], f[i-1][j+2]) + 1
對于正三角形, f[i][j] = min(f[i+1][j], f[i+1][j-1], f[i+1][j-2]) + 1
初始化, f[][] = 0(A[][] = '#'), f[][] = 1(A[][] = '-'); min(f[i][j])^2即為答案
*輸入需保留空格, 卡了30min(排除ASCII為10或13的字符即可)
*沒有考慮正方向, 大約2h時對照std發現
*有兩個點數據錯誤, 對照std后發現std僅當橫坐標為奇數是考慮倒三角形, 橫坐標為偶數時考慮正三角形, 而題目中無此限制
*學習利用批處理對拍的寫法
@echo off
:again
gen
trigon
trigon_me
fc trigon.out trigon_me.out > nul
if not errorlevel 1 goto again
選課course[樹形dp]
[做法1]多叉轉二叉
f[i][j]表示以i為根節點的樹中, 選擇j門課
f[i][j] = max(f[i.r][j], f[i.l][k] + f[i.r][j-k-1] + i.v) (0<=k<j)
初始化f[][] = 0
*無法記錄方案 -> gXX表示比較困難
[做法2]泛化物品
??? -> 想撞墻 -> 需要學習
通向自由的鑰匙key[樹形dp], 150min, zoj 2280
f[i][j]表示以i為根節點的數, 花費能量為j時可以拿到的最多的鑰匙數
f[i][j] = max(f[i.r][j], f[i.l][k] + f[i.r][j-k-i.c] + i.v) (o<=k<=j-i.c)
初始化f[][] = -1, 邊界處理f[i][j] = 0(i<=0 || j<0)
*記錄各點鄰接矩陣, 利用dfs構造樹(注意處理后取消鄰接), 并多叉轉二叉 -> 30min
*對于f[i.r][j]不必在記憶化搜索函數中遍歷所有兄弟, 只遍歷最近的即可
*注意讀題, 出發點為1, i.c和i.v非負 -> 1.5min
*注意靜態查錯, 如記憶化搜索中dp(i, j)打成f(i, j)的情況
*覺得比較暈的時候等一下再調題, 可以先干點別的, 這樣可以減少時間的浪費
警衛安排security[樹形dp], 100min
[狀態]
f[i][0]表示以i為根節點, 并在i安排警衛的最小花費
f[i][1]表示以i為根節點, i的父節點已安排警衛的最小花費
f[i][2]表示以i為根節點, i的子節點已安排警衛的最小花費
[方程]
f[i][0] = Σmin(f[i.son][0], f[i.son][1], f[i.son][2]) + i.v
f[i][1] = Σmin{f[i.som][0], f[i.son][2]} (i不是樹的根節點)
f[i][2] = min{Σmin{f[i.son][0], f[i.son][2]}(i.son != k) + f[k = i.son][0]}
[初始化]
對于葉節點, f[i][0] = i.v, f[i][1] = 0, f[i][2] = i.v
對于其他值, f[][] = -1
*對于根節點的尋找, 利用prev[i]記錄i的前驅, 若!prev[i], 則i為樹根
*結合批處理和makedata以及小范圍暴力程序, 可以有效地避免各種錯誤及極端情況 -> 需要學習搜索
*對于這類題目, 思考的關鍵在于分類寫出方程, 并注意方程的邊界條件(類似:tyvj 沒有上司的舞會)
*對于樹形dp, 存在兩種類型; 一種是對于加權路徑長度限制, 另一種則是求加權最值
8.4
青蛙的煩惱frog[區間dp]
初看是最小生成樹問題, 但是此題有幾個特別的性質:
1.以1號荷葉為起點, 終點不定
2.遍歷荷葉的最短路徑是一條鏈
3.題目給出的坐標順序是一個順時針方向的多邊形
4.最短路徑不相交(畫一個四邊形, 利用三角形性質可以觀察到)
根據性質1和2, 容易得出O(N^3)的方程, 很明顯會超時
f[i][j] = min(f[k][j-1] + d[i][k]) (i!=k)
-f[i][j]表示以i為起點, 長度為j的最短路徑, 初始化f[i][1] = 0
進而考慮性質3和4, 因而對于點1, 只能選擇相鄰的點2和n, 可以得到O(N^2)的方程
f[i][j][0] = min{f[i+1][j-1][0] + d[i][i+1], f[i+1][j-1][1] + d[i][i+j-1]}
f[i][j][1] = min{f[i][j-1][1] + d[i+j-1][i+j-2], f[i][j-1][1] + d[i+j-1][i]}
-f[i][j][0]表示以i為起點, 長度為j的最短路徑, f[i][j][1]表示以i為終點, 長度為j的最短路徑, 初始化f[][1][] = 0
-一個實現上的小優化, 保證d[i][j](i<j)
*注意靜態查錯, 區別變量名, 思考算法的過程應該長于調試的過程
*修正了測試點6
火車進站train[線性dp], 70min
1.M <= 3 -> 可以分類討論
2.只存在一條軌道, M只能決定軌道的長度 -> 如果同時在軌道中, i在j前的必要條件是i.s<=j.s和i.t<=j.t
3.小站工作人員可以任意安排這些火車進站的先后排列 -> 記憶化搜索
4.小站允許幾輛火車同時進站或出站 -> 所有條件都可取等號
M = 1, f[i] = max(f[j] + 1) (i.t <= j.s)
M = 2, f[i][j] = max(f[j][k] + 1) (i.t <= k.s)
M = 3, f[i][j][k] = max(f[j][k][l] + 1) (i.t <= l.s)
初始化f = 0, 利用vis記錄是否計算過, 各下標互不相等
*枚舉過程中注意剪枝, 利用i!=j和i.s<=j.s,i.t<=j.t逐層處理即可
*[讀題]明確要求的是什么, 存在哪些條件, 寫list
*[未驗證]先對以進站時間為第一關鍵字, 出站時間為第二關鍵字進行快排, 然后直接遞推, 下標滿足i < j < k < l
快餐問題meal[資源分配(不妨認為是背包)dp + 貪心優化] -> 類似, usaco 3.4.4 rocker
f[k][i][j]表示k條生產線生產i個漢堡, j個薯條時生產飲料的最大值, p[k][i][j]表示第k條生產線, 其他同.
f[k][i][j] = max{f[k][i-ki][j-kj] + p[k][i][j]}
sum[i] = sum[i-1] + A[i]
初始化f[1][i][j] = p[1][i][j], f[2..n][i][j] = -1, 復雜度O(N*100^4)
幾個優化
1.注意到每種物品總數小于100, 最大產量的上限是lim = min(100/a, 100/b, 100/c, sum[n]/(a*p1+b*p2+c*p3))
2.為了避免數組越界, i的上限是min(lim*a, sum[n]/p1), j的上限min(lim*b, (A[k] - i*p1)/p2);
ki的上限min(i, A[k]/p1), kj的上限min(j, (A[k] - ki*p1)/p2) -> 對于逗號右邊, 其實就是ki*p1+kj*p2<=A[k]
3.將程序中的min/max用if語句替代
4.對于每一套生產線, 盡量成套生產
[反例]
1 1 1
2 3 7
3
15 16 17
貪心可得最大值為3, 實際上15 = 7 + 3 + 3 + 2, 16 = 7 + 3 + 2 + 2 + 2, 17 = 7 + 7 + 3, 最大值為4;
利用優化1和2可以過5個測試點, 優化3可以進一步優化時間常數, 利用優化4可以過9個測試點
AC程序參見[此文]http://hi.baidu.com/zijingningmeng/blog/item/2761617e2afe7ae32e73b3b3.html
*調試過程中的主要問題是邊界溢出(沒有區分是否計算過), 變量名寫錯
*網上有說法表示去掉每種物品的件數限制后, 題目變成網絡流 -> gXX證偽
*比賽的話, 不妨小數據(n<5)DP, 大數據(n>=5)貪心, 這樣應該可以得到超過一半的分數
卡車更新問題truck, O(N^2), 1h
f[i][k]表示第i年某車已使用了k年
f[i][k] = max{f(i+1, 1) + R[0] - U[0] - C[k], f(i+1, k+1) + R[k] - U[k]}
初始化f[][] = -1, 邊界條件f[i][k] = 0(i>N,k>K), 利用記憶化搜索實現
記錄方案利用bool型數組prev[i][j][k]記錄是否購買新車, 遞歸即可
*盡可能不換新車
*利用樣例構造樹, 寫出狀態即可得到方程
*測試點1存在等價方案, 已修正數據(可能需要Special Judge)
*f[i][j][k]中i和k可以唯一確定狀態, 因而可以去掉中間1維
選課course[樹形dp + 記錄方案], 多叉轉二叉實現
f[i][j]表示以i為根節點的樹中, 選擇j門課
f[i][j] = max(f[i.r][j], f[i.l][k] + f[i.r][j-k-1] + i.v) (0<=k<j)
初始化f[][] = 0
[記錄方案]
利用print(i, j)遞歸, vis[i][j]表示是否已遍歷, [邊界]i∈[1, m], j∈[1, n]
right[i][j]表示f[i][j]是否等于f[i.r][j]
prev[i][j] = k表示f[i][j]由f[i.l][k],f[i.r][j-k-1]推得, 這時需記錄p[i] = 1
從1到n判斷p[i]直接輸出即可.
8.5
廣場鋪磚問題floor[狀壓dp], 2h
f[i][S] = Σf[i-1][S'] (S由S'推得)
初始化f[1][0] = 1, f[h+1][0]即為答案
對于每個f[i-1][S']利用dfs(當前行i, 當前行狀態s1, 下一行狀態s2, 當前行指針k)尋找S
if(!(s2 & 1<<k) && !(s2 & 1<<(k+1)))
dp(i, s1, s2, k + 2);//存在連續兩個空位, 即橫放
dp(i, s1, s2 ^ (1<<(k)), k + 1);//對當前位取反, 即豎放
利用int保存每一位的擺放方式, 1表示當前行被上一行占用, 0表示當前行未被占用
[邊界]k = 0, 遞歸過程中k > w則退出
硬木地板floor2[狀壓dp]
f[i][S] = Σf[i-1][S'] (S由S'推得)
初始化f[1][0] = 1, f[h+1][0]即為答案
*實現無能, 最終放棄 -> 我應該去學位運算優化BFS -_-
*魚牛《狀態壓縮》理解不能, NOI導刊朱全民文章code不全.
*耗時最長的題目往往不是表面上的難題, 而是那些被簡單估計的難題
7.13
p1057 金明的預算方案[分組背包], 1.5h
f[v] = max{f[v], f[v - c[i][j]] + w[i][j]}
*注意讀題,主件的編號和物品編號相同,這里調了1h
*注意逗號的使用
@Ural p1018 Binary Apple Tree[樹形], 1.5h{大量參考題解}
f[i][j] = max(f[tree[i].l][k] + f[tree[i].r][j-k-1] + tree[i].v)
*初始化中使用-1標記未計算(避免重復0)
*遞歸建樹 -> 尋找兒子的過程可利用鄰接表優化[未驗證]
*記憶化搜索f[t][q]初始化為0, 根節點值最后計算, 注意特殊情況0
*特別注意, 把題目中的 邊權 轉換為 點權, 以及q的相關變化
7.15
#p1051 選課[樹形DP], 1.5h
f[i][j] = f[tree[i].r][j] (左子樹空)
f[tree[i].l][k]+f[tree[i].r][j-k-1]+tree[i].v (左子樹非空)
*多叉樹轉二叉樹 -> 左兒子, 右兄弟
if (!left[a]) tree[a].l = i;
else tree[left[a]].r = i;
left[a] = i;
**記憶化搜索過程為什么不能直接返回int -> 實驗證實會引起錯誤, 原因不明 -> 盲目合并語句所致
if (f[i][j] || i == 0 || j <= 0) return 0;
應為
if (i == 0 || j <= 0) return 0;
if (f[i][j]) return f[i][j] ;
-> 合并此類控制邊界語句應注意返回值
**泛化背包做法 http://archive.cnblogs.com/a/2091585/
p1087 sumsets[完全背包+統計方案數], 60min
f[i][j] = f[i-1][j] + f[i][j-c[i]] (f[0][0] = 1)
一開始盲目列表找遞推式, 嘗試無果. 后發現題目本意即完全背包問題, 2^k是物體, 實現時注意降維.
*統計方案總數問題遞推式中max改為+, 注意f[0] = 1
*此類問題注意高精度的實現 或者 mod(注意題目中要求, 如本題9位精度)
*另一種方程 f[i] = f[i-1] (i=2k+1) -> 已通過觀察得到
f[i-1] + f[i/2](i=2k) -> 動機是什么?
p1079 數字三角形3[坐標DP], 30min
f[i][j] = max(f[i+1][j], f[i+1][j+1]) + A[i][j] (0 < j <= i <= n/2)
通過分析可知, 指定點(n/2, n/2)前(i, i)必取, 而其后和一般數字三角做法相同, 終點為f[n/2][n/2]
故最終答案Σf(i,i)_(0 < i < n/2) + f[n/2][n/2]
*坐標問題注意分析起點和終點的要求
p1084 數字三角形4[坐標DP], 10min
(x, y)前: f1[i][j] = max(f1[i-1][j-1], f1[i-1][j]) + A[i][j] (0 < j <= i <= x)
(x, y)后: f2[i][j] = max(f2[i+1][j], f2[i+1][j+1]) + A[i][j]
分析可知, (x, y)前順推, 指定終點為(x, y), 起點必然為(1, 1), (x, y)后逆推, 指定終點為(x, y)
故最終答案為f1[x][y] + f2[x][y] - A[x][y]
p1076 數字三角形2[判定性DP], 30min
f[i][j][(k+A[i][j])%100] = f[i+1][j][k] | f[i+1][j+1][k]
通過增加維度轉化為判定性問題, 由于取模所以k的順序不確定, 因而用坐標來控制順序
7.16
#p1048 田忌賽馬[貪心 + DP], 1.5h
1.O(N + NlogN), [題解來自網絡]思想是這樣的, 先把各組馬的速度從大到小排序, 然后用田忌的馬順序與齊威王的馬比較
if(田忌的馬快)比較下一對馬;
else if(田忌的馬慢)用田忌最慢的馬和齊威王的這匹馬賽
else{
從未進行比賽的速度小的馬開始從后往前比
if(田忌的馬快) //這里是必須的,否則如果是90 73 71 和 90 70 70 ,那么沒有這個是
繼續往前比 //2-1,有了的話就是2+0,非常重要
else 用這匹馬和剛才跑平的齊威王的馬比
//總之原則就是如果這匹馬不能贏,就讓他和比他快很多的馬比,這樣保持速度較快的馬
}
*while循環條件, f1 <= r1
2.O(N^2 + NlogN), 來自:http://hi.baidu.com/lyltim/blog/item/57fccd1153ea851eb9127ba9.html
[貪心分析]
1、如果田忌剩下的馬中最強的馬都贏不了齊王剩下的最強的馬,那么應該用最差的一匹馬去輸給齊王最強的馬。
2、如果田忌剩下的馬中最強的馬可以贏齊王剩下的最強的馬,那就用這匹馬去贏齊王剩下的最強的馬。
3、如果田忌剩下的馬中最強的馬和齊王剩下的最強的馬打平的話,可以選擇打平或者用最差的馬輸掉比賽。
[DP做法]
f[i,j]=max{f[i-1,j]+g[n-(i-j)+1,i],f[i-1,j-1]+g[j,i]}
其中g[i,j]表示田忌的馬和齊王的馬分別按照由強到弱的順序排序之后,田忌的第i匹馬和齊王的第j匹馬賽跑所能取得的盈利
#p1402 烏龜棋[路徑DP], 1.5h
f[i][j][k][l] = max(f[i-1][j][k][l], f[i][j-1][k][l], f[i][j][k-1][l], f[i][j][k][l-1]) + A[i+2j+3k+4l+1]
以卡片數為階段, 狀態f[i][j][k][l]表示還剩下每種牌各多少張時得到的最大值, 注意起始位置
*卡了1h因為被05的過河和08的傳紙條限制思維, 認為以所在位置為階段, 想對空間降維
*[降維條件]狀態各維度存在等量關系, 因而可減少時間復雜度, 但是不能改變空間復雜度
#p1052 沒有上司的舞會[樹形DP], 1.5h
f[i][0] = ∑max{f[j][0], f[j][1]} (j∈i.son), i不參加
f[i][1] = ∑f[j][0] + tree[i].v (j∈i.son), i參加
[邊界]若i為葉節點, f[i][0] = 0, f[i][1] = tree[i].v
前半個小時寫完了多叉轉二叉, 證明了left[]的必要性.
*狀態設計問題: 沒有區分i參加和不參加的情況, 并認為f[i]由f[j](不取i, j是i的兒子)和f[k](取i, k是i的孫子)推得
*葉節點的初始化, 對于f[i][]求和而非取最大值, 選取根節點而非葉節點(需要兩個數組映射)
*由于30min時方程考慮不周, 導致多次修正, 因而卡了1h. 務必要先寫出正確方程.
7.18
p1134 CCR的中考之考前計劃[模擬], 50min
語文題, 題目描述問題很大, 浪費了0.5h, google了一個std之后得到正確題意. 題目是類似beads的模擬題, 將環從某處打斷, 使得兩端科目類型相同的天數最多.尋找相同科目的條件A[j+1] = 'w' || A[j+1] = A[i].
*環狀問題的處理方法: 2n-1, 在本題中雙方向同時進行不妨3n-2
#p1088 treat[區間DP], 20min
f[i][j] = max{f[i+1][j] + A[i] * (n+i-j), f[i][j-1] + A[j] * (n+i-j)} (0 < i < j <= n)
f[i][j]表示[i, j]未取時的最大值, 初始化f[i][j] = A[i] * n, 以長度l為階段, 故 j = i+l-1
*考慮第k次取數, k = n - (i-j+1) + 1(包括這次), 昨天沒想到這點卡了很久
*在網上找到了另外一種設置狀態的方法, 設f[i][j]是取i個數, 左邊取j個, 方程:
f[i][j] = max(f[i - 1][j - 1] + i * A[j], f[i - 1][j] + i * A[n - (i - 1 - j)])
-> 猜想動機: 存在等式 前 + 后 = 總數, 狀態的設置都是為了描述著三個量.
agirnet[Krusal], 20min
復習并查集實現的Krusal
p1307 聯絡員[Krusal],50min
必選邊先使用set[find(e[i].u)] = find(e[i].v)合并, 并記錄權和, 然后按一般的Krusal做即可.
*使用stdlib.h的qsort間接排序失敗, 原因不知(20min)
*注意此時k++不能并入下一行語句中, 否則++k和k值不同導致輸入錯誤:
++k,
scanf("%d%d%d", &must[k].u, &must[k].v, &must[k].w);
7.20
p1113 魔族密碼[LIS模型], 40min, 6WA
f[i] = max{f[j] + 1} (A[j]為A[i]前綴, 1 <= j < i)
*注意最大值不一定在f[n]中, 需要對f[1] -> f[n]進行循環檢查, 卡了30min
p1187 小飛俠的游園方案[0/1背包], 15min
f[i][j] = max(f[i-1][j], f[i-1][j - c[i]] + w[i])
存在可能未裝滿的情況, 故循環檢查f[n][]即可
#p1190 積木城堡[背包DP], 1.5h, 6WA
f[k][j] = f[k][j - w[i]] (j - w[i] >= 0)
類似分組背包的做法, 記錄每組物品的所有可能值, 若f[1..k][V]同時為true, 則V為最值. 也可以在讀入時, 循環檢查每組物品的可能值.
*注意讀題, 尤其是各種數據范圍, 不要重復去年第二題!!!
*讀入時注意MAXn+1, 留意-1的情況; 顯然答案不會超過所有城堡的最小高度(而非最大).
*題目中并沒有強調按順序取積木, 因而一開始打了模擬, 之后手賤去Google. 對于這類問題, 在提交后若發現則應繼續思考. 此外不要給題目增加條件.
**注意這類確定各組物品所有可能值寫法 和 最優值寫法的區別
*一組測試數據:
5
87 76 65 54 32 21 23 -1
64 75 25 63 76 23 75 13 64 23 -1
09 78 76 46 32 45 23 -1
23 34 45 -1
12 34 23 -1
p1213 嵌套矩形[LIS模型/DAG最長路], 1h, 9WA
(1)f[i] = max(f[j] + 1) (rect[i]可嵌套rect[j])
*初始化f[] = 1; 初始化為0, 輸出+1會導致錯誤, 原因不知.
-> 另一種寫法(by Ylen): f[0] = 0, f[] = INT_MAX;
*注意題目沒有強調矩形間存在順序, 因而存在后效性. 易知面積小的矩形不會嵌套面積大的矩形, 因而以面積為關鍵字對rect進行間接排序.
(2)f[i] = max(f[j] + 1 | (i,j)∈G)
若rect[i]可嵌套rect[j], 則建立一條從i到j的邊, 求最長路即可
記得去年在紙上寫過一份這樣的東西,迎來的是exhaustive fail.需要明確復習重點是數據結構和算法, 考場上要注意出題規律和草稿的清晰性. 簡單復習大概用了6個番茄鐘.
1.表達式樹
[中綴] (a + b) * c + d * c
(((a + b) * c) + (d * c))
[前綴] +(*(+(a b) c) * (d c))
+ * + a b c * d c
[計算方法1] 壓棧(前綴 -> 從后向前)
[計算方法2] 括號(opt后) -> 中綴
2.二分查找的平均次數 log(n+1)-1
3.heap的實現
[up] while(k > 1) 比較 交換(h[k], h[k/2])
[down] while(2 * k <= n) 比較 交換(h[k], h[2k + 1(小于n的話)])
4.排序算法的穩定性
[穩定]插入排序、冒泡排序、歸并排序、分配排序(桶式、基數)
[不穩定的]直接選擇排序、堆排序、shell排序、快速排序都是不穩定的排序算法。
[穩定排序]http://www.jpk.pku.edu.cn/pkujpk/course/sjjg/chapter7/01/05.html
[排序原理]http://hi.baidu.com/cuifenghui/blog/item/0587932b039557f9e7cd4051.html
5.出棧序列
一個數列滿足條件,當且僅當每一段單調遞減數列要么不存在空缺(即為公差-1的等差數列),要么它的空缺在之前全部已經出現。
[充要條件]若出棧序列合法,則一定不存在1<=i<j<k<=n,使s[j]<s[k]<s[i]
6.鄰接表的插入操作(鏈表的插入)
next[e] = first[u[e]]; first[u[e]] = e;
7.邏輯表達式恒值
注意符號, 轉化為分情況表示的函數
8.叉積的計算(特別注意第二個負號)
|i j k|
|a1 a2 a3|=i|a2 a3|-j|a1 a3|+k|a1 a2|
|b1 b2 b3| |b2 b3| |b1 b3| |b1 b2|
9.閱讀程序注意意圖, 草稿的清晰度(減少亂劃)
[RAM]http://baike.baidu.com/view/3558.htm
隨機存儲: 訪問時間與位置無關
[Hash]http://www.nocow.cn/index.php/%E6%95%A3%E5%88%97%E8%A1%A8
[拓撲排序(DAG)]http://zh.wikipedia.org/wiki/%E6%8B%93%E6%92%B2%E6%8E%92%E5%BA%8F\