DP提高专项1题解
主要讲一下每一题的做法以及思考方式。
感觉难度薇 \(T1-T3-T2\) 。但是都不算难。
\(T1\)
题目描述
Jimmy 最近迷上了一款叫做方块消除的游戏。游戏规则如下:\(n\) 个带颜色方格排成一列,相同颜色的方块连成一个区域(如果两个相邻方块颜色相同,则这两个方块属于同一区域)。为简化题目,将连起来的同一颜色方块的数目用一个数表示。
例如,9 122233331
表示为
4
1 2 3 1
1 3 4 1
游戏时,你可以任选一个区域消去。设这个区域包含的方块数为 \(x\),则将得到 \(x^2\) 个分值。方块消去之后,其余的方块就会竖直落到底部或其他方块上。而且当有一列方块被完全消去时,其右边的所有方块就会向左移一格。Jimmy 希望你能找出得最高分的最佳方案,你能帮助他吗?
第一行包含一个整数 \(m\)(\(1 \le m \le 50\)),表示同颜色方块区域的数目。
第二行包含 \(m\) 个数,表示每个区域的颜色(\(1\) 到 \(m\) 之间的整数)。
第三行包含 \(m\) 个数,表示每个区域包含的方块数(\(1\) 到 \(20\) 之间的整数)。
思路点拨
挺简单的一题。考虑到本题从序列中间开始操作会得到不同的结果,所以不可以使用类似于子序列提取的线性dp,而是考虑区间dp。
我们比较头疼的是,在消除完之后右边的方块会挪过来,所以我们可以在状态中体现这一点。我们定义 \(f_{l,r,k}\) 表示是目前考虑了区间 \(l,r\) 的方格,右端点右边接了 \(k\) 个与之颜色相同过的方块的最大价值。
转移分两类讨论:
- 把右边炸了!
- 考虑炸掉中间一段,然后右边的 \((num[r]+k)\) 移到左边去(要求左边与右边颜色相同)
别的不需要多多考虑,因为我们最为简单的方式就是从左到右依次炸掉,我们不论怎么操作,答案不会劣于这个。所以我们只需要考虑让颜色相同的尽量在一起炸掉。
时间复杂度 \(O(n^4)\) 。
代码很短,不放了。
\(T3\)
题目描述
现在有 \(n\) 个元素,构成一颗二叉排序树。还知道这颗二叉排序树父子之间权值不互质,问是否存在这样的树?
$n \leqslant 700 $ 。
思路点拨
二叉排序树的性质:中序遍历是有序的。我们考虑对原序列排序之后区间 \(dp\) ,按照中序遍历 \(dp\)。
因为需要父子之间权值互质,所以我们在状态里记录一下根。设 \(f_{l,r,k}\) 表示考虑到区间 \(l,r\) ,根位 \(k\) 是否可以满足条件。那么
时间复杂度 \(O(n^4)\) ,不可以通过。
考虑优化,我们发现我们做了一个十分智障的操作。我们在状态里面记录根的目的就是为了保证根与根的父亲不互质,但是没有必要将根表示出来。因为对于区间 \([l,r]\) 而言,它的根的父亲无非就是 \(l-1,r+1\) 两种。所以我们可以更改一下状态:
\(f_{l,r}\) 表示考虑到区间 \([l,r]\) ,根的父亲是 \(l-1\) 的方案。
\(g_{l,r}\) 表示考虑到区间 \([l,r]\) ,根的父亲是 \(r+1\) 的方案。
转移方式几乎不变,枚举一个根(\(vis\) 数组表示是否互质):
for(int len=2;len<=n;len++)
for(int l=1,r=len;r<=n;l++,r++)
for(int mid=l;mid<=r;mid++){
if(vis[mid][r+1]) f[l][r]|=(f[l][mid-1]&g[mid+1][r]);
if(vis[mid][l-1]) g[l][r]|=(f[l][mid-1]&g[mid+1][r]);
}
时间复杂度 \(O(n^3)\) 。
\(T2\)
题目描述
Dreamoon 有一个字符串 \(s\) 和一个模式串 \(p\),他会先从 \(s\) 中删除恰好 \(x\) 个字符来产生一个新的字符串 \(s'\)。然后他会计算 \(occ(s',p)\),即从 \(s'\) 中能找到的等于 \(p\) 的不相交的子串数量的最大值。他想让 \(occ(s',p)\) 的值尽可能大。
更形式地说,让我们用 \(ans(x)\) 表示所有可以从 \(s\) 中删去恰好 \(x\) 个字符得到的 \(s'\) 中 \(occ(s',p)\) 的最大值。Dreamoon 想要知道对于所有的 \(x\) \((0 \leq x \leq |s|)\),\(ans(x)\) 的值。
\(|s| \leqslant 2000, |p| \leqslant 500\) 。
思路点拨
本场比赛最有思维含量的一题,比较靠设状态。
看到题目,这题挺 \(\text{KMP}\) 的。我们定义状态 \(f_{i,j,k}\) 表示目前考虑到下标 \(i\) ,删除了 \(j\) 个字符,目前 \(\text{KMP}\) 的失配指针在 \(p\) 字符串的下标 \(k\) 时,以及匹配的 \(p\) 的数量的最大值。
转移可以刷表。对于 \(f_{i,j,k}\) 而言,我们考虑 \(i+1\) 的状态:
-
删了
-
不删,此时维护 \(\text{KMP}\) 的失配指针。
特别好想,时间 \(O(|s|^2|p|)\) ,不可以通过。
发现,上面这个 \(dp\) 因为依赖了 \(\text{KMP}\) 这个类似于自动机的东西,所以我们的第三维无法优化掉,所以我们换思考方向,将 \(p\) 作为一个整体去思考。
那么我们又可以得出如下状态:
\(f_{l,r}\) 表示区间 \(l,r\) 是否可以提取出子序列 \(p\) ?如果可以,那么 \(f_{l,r}=r-l+1-|p|\) ,如果不可以 \(f_{l,r}=inf\) 。这是很好预处理,不讲。
\(dp_{i,j}\) 表示考虑到下标 \(i\) ,删除了 \(j\) 个字符的情况下,匹配 \(p\) 的最大值。转移比较明了:
时间复杂度 \(O(|s|^3)\) 。虽然比较劣,但是我们发现状态有优化的空间。
我们假设,\(g_i\) 表示从下标 \(i\) 往左边找,能够平凑处 \(|p|\) 这个子序列的最大下标。从 \(g_i\) 开始,我们的 \(f\) 数组 \(+1\) 。\(g_i\) 之前的东东我们可删可不删,而只有在 \([g_i,i]\) 之间的元素我们必须删除,我们可以简单改一下状态。
\(dp_{i,j}\) 表示考虑到下标 \(i\) ,至少 删除了 \(j\) 个字符的情况下,匹配 \(p\) 的最大值。转移比较明了:
大概就是这样,优势在于决策点减少了。具体细节不讲。
时间复杂度 \(O(|s|(|s|+|p|))\)