#
[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\