DP小练终于搞定了,前前后后用了两星期,这次练习,主要是练习数位,概率和树形dp,其中不乏有些很好的思想。
首先,我们来看看数位dp。一般的,数位dp都用于统计区间,满足一些性质的数的个数,格式都比较固定,究其本质,不外乎先查找【0, r】区间满足该性质的数的个数,然后再求出【0, l - 1】区间满足该性质的数的个数,然后相减即可。
这里给出一段我的模板
int digit[70];
ll dp[65][22][22];
ll dfs(int pos, int cnt4, int cnt7, int flag) {
if(cnt4 > x || cnt7 > y) return 0;
if(pos < 1) return (cnt4 == x && cnt7 == y);
ll &ret = dp[pos][cnt4][cnt7];
if(!flag && ret != -1) return ret;
int up = flag ? digit[pos] : 9;
ll ans = 0;
for(int i = 0; i <= up; i++) {
ans += dfs(pos-1, cnt4 + (i == 4), cnt7 + (i == 7), flag && i == up);
}
if(!flag) ret = ans;
return ans;
}
ll cal(ll n) {
int len = 0;
while(n) {
digit[++len] = n % 10;
n /= 10;
}
return dfs(len, 0, 0, 1);
}
有时候,数位dp会存在一些变形,例如HDU3943要求找到具体的满足条件的区间内的最大值,这个我们利用二分搜索,枚举答案,在上一次dp的基础上,继续做dp,不断缩小解空间,找到最优解。
再例如HDU3709,我们需要枚举平衡位置,来进行dp然后查找最优解否则无法统计。
概率dp获益良多。
POJ3744,如果横着扫一遍 统计过去,那么时间开销太大了,由数据,我们发现埋雷的位置很少,我们从这下功夫,然后在画画,可以发现,决策为f[n] = a * f[n-1] + b * f[n-2],其中a, b是常数。于是,枚举雷前面的位置,然后用矩阵快速幂加速显然。
概率dp一般是顺着来的(因为起点为1的话,他的概率就为1,往后顺推容易计算)
期望dp一般是倒着来的(通常,把达到的目标状态期望设定为0)
概率dp和期望dp并不是很难,但是当我们根据题设条件列出方程的时候,化简是比较烦的,这时候需要的是细心和耐心。
再者,概率和期望想要学好,论文是必须要看的。
ZOJ3329是比较基础的,容易推到方程。
当我们遇到类似于POJ2151这种题,我们需要顺着+倒着想一次,然后才能利用条件概率的知识解出正确答 有时候,推导出来的方程,往往依赖于之前未计算出来的状态(多见于期望dp),我们可能需要迭代,或者分拆方程的方法,例如HDU4035,后面的值依赖于前面的节点,我们需要改变下原状态方程,用数学代换,再迭代,解出辅助方程的转移方法。
最后再谈谈树形dp,
树形dp有两种思维方式,一种是点分治,也就是说,当我们计算一个点时候,我们只需要计算以这个点为根的子树(这是理想情况下,蓝儿多数时候,是需要考虑到父亲甚至祖先节点的,这时候,我们长可以用陈启峰一张一弛解题之道的思想,科学的缩小或者放大解空间,直戳题目要害,问题才能迎刃而解)。还有一种是边分治,例如找树的重心。
此博客会根据以后对dp的理解持续更新。