区间和数列上的DP
区间和数列上的DP
-
序列 \(dp\) 上的问题
-
组合数学的经典数列:卡特兰数
-
序列 \(dp\) 问题一种常见的优化方法:单调队列优化
-
括号括号序列问题
-
区间 \(dp\) 状态设计的一般形式
-
区间 \(dp\) 处理环形问题
-
区间 \(dp\) 转移一般考虑区间边界的情况
序列上的DP
◦ \(F[i]\) 表示以 \(i\) 结尾的最优值或方案数。
◦ \(F[i][k]\) 表示以 \(i\) 结尾附加信息为 \(k\) 的最优值或方案数, 转移的话往往是枚举上一个断点。
◦ \(F[i] = max~~~(F[j] + w(j+1,i) |~j~是一个满足转移条件的断点)\)
T1
有 \(m\) 个码头和 \(e\) 条航线,每天航线有成本。有连续 \(n\) 天需要从 \(1\) 号码头到 \(m\) 号码头运输货物。每个码头会在某些天数区间内不许经过。每更换一次运输路线,要付出 \(k\) 的成本
求这 \(n\) 天的最小总成本
\(m<=20,n<=100\)
solution
(刚开始理解错题意了 = =)
可以将问题转化为:货物运输要运输 \(n\) 次,可以把这 \(n\) 次划分成几段,每一段选用相同的路线;
所以我们应该枚举这些天的断点,利用断点进行转移(区间常见思路) 对于每一段的花费,去除这些天不能走的点,求出的最短路 * 这些天数(次数),就好了
先处理一个 \(w(x, y)\) 表示在第 \(x\) 天到第 \(y\) 天的最小成本,把能在这些天走的点加进去跑最短路
设状态
\(f[i]\) 表示前 \(i\) 天的运输最小成本,用 \(j\) 来枚举断点(何时转移路径)
转移式
时间复杂度
\(O(N^2 * m * log(m))\)
T2
有 \(n\) 条木板要被粉刷,每条木板分为 \(m\) 个格子,每个格子需要被刷成蓝色或红色。
每次粉刷可以在一条木板上给连续的一段格子刷上相同的颜色。每个格子最多被刷一次。
问若只能刷 \(k\) 次,最多正确粉刷多少格子。
\(n,m<=50,k<=2500\)
solution
考虑只有 \(n=1\) 的情况,发现能正确粉刷的数量和粉刷的位置有关,如果再某个位置粉刷,后面相同的颜色可能都被粉刷正确(或者都错),所以我们枚举粉刷的位置和次数就好了,
\(w(i,j)\) 是 \((i,j)\) 内相同颜色的数量,哪个颜色多刷哪种颜色,前缀和求一下就好了
状态:
设 \(g[i][j]\) 表示前 \(i\) 个格子刷 \(j\) 次的最多正确格子
转移:
如果多个木板??
我们再设一个 \(f[i][j]\) 表示前 \(i\) 个木板刷 \(j\) 次的最大答案
转移
括号序列模型及解法
栗题
给定一个长度为 \(n\) 的仅包含左括号和问号的字符串,将问号变成左括号或 右括号使得该括号序列合法,求方案总数。
例如 \((())\) 与\(()()\)都是合法的括号序列。
\(n<=3000\)
solution
这种问题把左括号看成 \(+1\) ,右括号看成 \(-1\),保证任意一个前缀和大于等于0,且总和为0,就代表是个合法括号序列了
状态
\(dp[i][j]\) 表示当前到第 \(i\) 个字符,现在还有 \(j\) 个左括号
转移
分类讨论即可
若第 \(i+1\) 个字符是左括号,则能转移到 \(dp[i+1][j+1]\)
若第 \(i+1\) 个字符是右括号,则能转移到 \(dp[i+1][j-1]\)
若第 \(i+1\) 个字符是问号,则能转移到 \(dp[i+1][j-1]\) 与 \(dp[i+1][j+1]\)
答案: \(dp[n][0]\)
时间复杂度 :\(O(n^2)\)
T1
给出一些括号序列,要求选择一些括号序列拼接成一个合法的括号序列, 使得总长最大
\(1<=n<=300\),表示括号序列的个数
括号序列的长度 \(len\) 不超过 \(300\).
solution
发现:
(((()))) <=> empty
)(((())))( <=> )(
)())))( <=> ))))(( =?= ))))(
括号自身通过自身的匹配,发现对其他括号有影响的只有其中一部分,我们把这一部分记作 \((x,y)\)
然后就考虑每个子集怎么拼接更优了(先看下面栗题)
引入下一个问题
追栗
在一款电脑游戏中,你需要打败 \(n\) 只怪物(从 \(1\) 到 \(n\) 编号)。为了打败第 \(i\) 只怪物,你需要消耗\(d[i]\) 点生命值,但怪物死后会掉落血药,使你恢复 \(a[i]\) 点生命值。任何时候你的生命值都不能降到 \(0\)(或 \(0\) 以下)。
请问是否存在一种打怪顺序,使得你可以打完这 \(n\) 只怪物而不死掉
\(N<=10^5\)
solution
要考虑打败所有的怪兽,要保证每个时刻的血量都要大于等于 \(0\) ,;
考虑贪心(按照某种顺序排序)
考虑这道题的贪心顺序
-
如果 \(a[i] - d[i] > 0\) 说明打掉这个怪物的可以回血, 所以肯定是先打这种怪物,将它们按照伤害从小到大排个序,然后依次杀掉即可
-
如果 \(a[i]-d[i]<0\),说明会亏血。我们考虑最后剩余的血量值(逆向思维),倒序,假设是 \(x\),那么 \(x\) 是固定的。然后可以看作初始血量为 \(x\),怪兽的属性 \(a,d\) 交换,
这样就和上一种情况一样了,只需要用上面的方法倒着排一个序就好
可以借助数形结合的思想
返回去看上一个题
我们还是把左括号看成 \(+1\),右括号看成 \(-1\),同样是保证任意一个前缀大于等于 \(0\),且总和为 \(0\)
那就是每一个给定的序列都是先 \(-Li\) 再 \(+Ri\),\(Li\) 是对消后左端右括号的数量, \(Li\) 是对消后右端左括号的数量,将压缩后的每个括号序列,求出它们 \(-Li\) 和\(+Ri\)
然后按照上述打怪物的思路排个序,然后依次拼起来之后任何一个前缀都大于等于 \(0\),最后的和等于 \(0\)
solution
状态
设 \(f[i][j]\) 为前 \(i\) 个括号序列左括号比右括号多 \(j\) 个时的最长的长度和
转移
考虑下一个括号选还是不选
\(Len[i]\) 为排完序后第 \(i\) 个括号序列的长度
\(f[i+1][j-L[i+1]+R[i+1]]\) <= \(f[i][j] + len[i+1] (j>=L[i+1])\)
\(f[i+1][j]\)<= \(f[i][j]\)
最后答案就是 \(f[n][0]\)
复杂度 \(O(n*len*len)\)
卡特兰数
① \(1,2,3…n\) 以此进栈,求有多少种可能的出栈序列
② 由 \(n\) 对括号形成的合法的括号序列由多少个
\(N<=10^5\)
② 要匹配成合法的,那么左括号数量始终要大于等于右括号,且最终右括号数量等于左括号数量
所以就有个下列式子
这就是卡特兰数的公式
更为简洁的表达方式
① 把进栈看成左括号,出栈看成右括号,任何时候出栈都不能大于入栈总数,所以问题就相同了
类似于一个问题划分成两部分,求方案数的就是卡特兰模型
dp思想: 注意一段一段划分我们可以枚举最后一段的起点,但是这里不是一段一 段的,考虑另外的转移方式。实际上我们发现我们可以枚举 \(1\) 这个数是什么时候出栈的。那么我们就可以得到式子
常见卡特兰数模型
③ \(n\) 个节点共能构成多少种二叉树,左右子树是认为不同。
④ 凸多边形的三角划分的方案数:把一个凸多边形用 \(n-3\) 条直线连接 \(n-3\) 对顶点,共形成 \(n-2\) 个三角形,求方案数
⑤一个 \(n*n\) 的格子,从 \((0,0)\) 走到 \((n,n)\),求不跨过 \((0,0)->(n,n)\) 这条直线的路径方案数
经典题
有 \(n\) 个数,选择其中若干数,使得每连续 \(k\) 个数中都至少有一个数被选中,且选出的数的和最小
\(k<=n<=1000\)
\(k<=n<=100000\)
状态
\(dp[i]\) 表示前 \(i\) 个数满足题目要求且第 \(i\) 个数被选中,这样的情况下选出的数的和最少是多少
转移
通过枚举上一个被选出的数 \(j\) 在哪里
时间复杂度
\(n^2\)
发现这是一个典型的滑动窗口模型
单调队列优化
观察式子 \(dp[i]~=~min(dp[j])+a[i]~~~(i-j<=k)\)
分两种
对于两个决策 \(j_1,j_2\) ,满足\(j_1 < j_2\)
- 若\(dp[j_1] < dp[j_2]\), 则当\(i-j_1 > k,i-j_2<=k\) 时,\(j_2\)能代替 \(j_1\)(利用区间限制将\(j_1\)弹出)
- 若 \(dp[j1]>=dp[j2]\) ,则无论何时,\(j_1\) 都不可能比 \(j_2\) 优,可以直接删除
每次在队列末尾插入删除一个数或者在队首删除一个数,且该队列始终保持单调递增。
因此称为单调队列优化。
每个数进入队列出队列一次,转移是 \(O(1)\) 的,总时间复杂度为 \(O(n)\)
node
int l = 1,r = 0;
for (int i = 1;i <= n; i++){
while(l <= r && q[l] < i - m)l++
dp[i] = a[i] + dp[q[l]];
while(l <= r && dp[i] <= dp[q[r]])r--;
q[++r] = i;
}
区间dp
区间 \(dp\) 一般就是设 \(dp[i][j]\) 表示区间 \([i,j]\) 所能形成的最优答案或者方案数
栗题
有 \(n\) 堆石子,每次只能合并相邻的两堆石子,并且消耗相邻两堆石子个数 的体力值,问最少消耗多少体力值将 \(n\) 堆石子合并为 \(1\) 堆
\(N<=100\)
solution
状态
\(dp[i][j]\) 表示将区间 \([i,j]\) 这段区间内的石子合并为 \(1\) 堆的最小体力值
转移
考虑对于区间 \([i,j]\) 它一定是由两段区间 \([i,k]\),\([k+1,j]\) 合并成的,所以转 移就考虑枚举 \([i,j]\) 区间内的一个分割点 \(k\) 转移即可
\(sum[i,j]\) 用前缀和搞一下就好了
node
for (int i = n; i >= 1; i--){
for (int j = i; j <= n; j++){
if(i == j) fmin[i][j] = fmax[i][j] = 0;
else{
fmin[i][j] = 0x3f3f3f3f;
for (int k = i; k < j; k++){
fmax[i][j] = max(fmax[i][j], fmax[i][k] + fmax[k + 1][j] + s[j] - s[i - 1]);
fmin[i][j] = min(fmin[i][j] ,fmin[i][k] + fmin[k + 1][j] + s[j] - s[i - 1]);
}
}
}
}
栗题
T1
给定一个字符串 \(S,Q\) 组询问,每次询问区间 \([l,r]\) 内有多少回文子串。
\(|S|<=1000\)
solution
状态
\(dp[i][j]\)表示区间 \([i,j]\) 有多少回文子串
转移
简单充斥
是\([i,j-1]\) 内的回文串 \(+[i+1,j]\) 的回文串-多算的 \([i+1,j-1]\) 内的回文串+判断\([i,j]\)是否是回文串
T2
给你长度为 \(m\) 的字符串,其中有 \(n\) 种字符,每种字符都有两个值,分别是插入这个字符的代价,删除这个字符的代价,让你求将原先给出的那串 字符变成一个回文串的最小代价
\(M<=2000\)
solution
很明显三种情况,加字符,减字符,啥也不干自动匹配
状态:
1 .\(dp[i+1][j]\) 表示区间 \(i\) 到区间 \(j\) 已经是回文串了的最小代价,那么对于\(s[i]\) 这个字母,我们有两种操作,删除与添加,对应有两种代价, \(dp[i+1][j]+add[s[i]]\) 或 \(dp[i+1][j]+del[s[i]]\),取这两种代价的最小值。
2 .\(dp[i][j-1]\) 表示区间 \(i\) 到区间 \(j-1\) 已经是回文串了的最小代价,那么对于\(s[j]\) 这个字母,同样有两种操作,\(dp[i][j-1]+add[s[j]]\) 或 \(dp[i][j-1]+del[s[j]]\),取最小值
3 .若是 \(s[i]==s[j]\),\(dp[i+1][j-1]\) 表示区间 \(i+1\) 到区间 \(j-1\) 已经是回文串的最小代 价,那么对于这种情况,我们考虑 \(dp[i][j]\) 与 \(dp[i+1][j-1]\) 的大小........然后 \(dp[i][j]\) 取上面这些情况的最小值
区间dp处理环形问题
处理方法:断环为链,将这个链复制一遍放在原链后面,然后做区间 \(dp\),最后答案就在,\(dp[i][i+n-1]\) 里面取最优的即可
栗题
在项链上有 \(N\) 颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标 记一定等于后一颗珠子的头标记。如果前一颗能量珠的头标记为 \(m\),尾标记为 \(r\),后一颗能量珠的头标记为 \(r\),尾标记为 \(n\),则聚合后释放的能量为 \(m×r×n\)(Mars单位),新产生的珠子的头标记为 \(m\),尾标记为 \(n\)
需要时,Mars人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直 到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是 不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
N<=100
solution
状态:
设\(f[j][i]\) 表示左端点为 \(j\) 号珠子,右端点为 \(i\) 号珠子的区间所能得到的最大能 量,
转移:
就枚举最后一步聚合的位置(断点)即可
区间dp转移套路
一般 \(n=1000\) 的区间 \(dp\) 问题,由于状态就是二维的了,转移一般都是 \(O(1)\), \(dp\) 转移过程中主要考虑就是 \(L\) 和 \(R\) 两个边界的情况,正如之前的2道题
而 \(n=100\) 的区间 \(dp\),除了边界往往还要枚举这个区间从哪个位置划分