Loading

区间和数列上的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\) 来枚举断点(何时转移路径)

转移式

\[f[i]=min(f[j]+k+w(j+1,i)*(i-j)~ |~ j<i ) \]

时间复杂度

\(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\) 次的最多正确格子

转移:

\[g[i][j]=max( ~g[k][j-1]+~w(k+1,i)~ |~ k<i ) \]

如果多个木板??

我们再设一个 \(f[i][j]\) 表示前 \(i\) 个木板刷 \(j\) 次的最大答案

转移

\[f[i][j]=Max(~ f[i-1][k]+g_i [m][j-k]~ |~k<=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\) ,;

考虑贪心(按照某种顺序排序)

考虑这道题的贪心顺序

  1. 如果 \(a[i] - d[i] > 0\) 说明打掉这个怪物的可以回血, 所以肯定是先打这种怪物,将它们按照伤害从小到大排个序,然后依次杀掉即可

  2. 如果 \(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\)

② 要匹配成合法的,那么左括号数量始终要大于等于右括号,且最终右括号数量等于左括号数量

所以就有个下列式子

\[f[n] = \sum^{n-1}_{i=0}f[i]*f[n-1-i] \]

这就是卡特兰数的公式

更为简洁的表达方式

\[{{2*n}\choose {n}} -{{2*n}\choose n - 1} \]

\[或 \frac{{2*n}\choose{n}}{n+1} \]

① 把进栈看成左括号,出栈看成右括号,任何时候出栈都不能大于入栈总数,所以问题就相同了

类似于一个问题划分成两部分,求方案数的就是卡特兰模型

dp思想: 注意一段一段划分我们可以枚举最后一段的起点,但是这里不是一段一 段的,考虑另外的转移方式。实际上我们发现我们可以枚举 \(1\) 这个数是什么时候出栈的。那么我们就可以得到式子

\[f[n] = \sum^{n-1}_{i=0}f[i]*f[n-1-i] \]

常见卡特兰数模型

\(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\) 在哪里

\[dp[i]=min(dp[j]+a[i],~dp[i])~~i-j<=k \]

时间复杂度

\(n^2\)

发现这是一个典型的滑动窗口模型

单调队列优化

观察式子 \(dp[i]~=~min(dp[j])+a[i]~~~(i-j<=k)\)

分两种

对于两个决策 \(j_1,j_2\) ,满足\(j_1 < j_2\)

  1. \(dp[j_1] < dp[j_2]\), 则当\(i-j_1 > k,i-j_2<=k\) 时,\(j_2\)能代替 \(j_1\)(利用区间限制将\(j_1\)弹出)
  2. \(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\) 转移即可

\[dp[i][j]=min(dp[i][k]+dp[k+1][j]~ |~ i<=k<j)+sum[i,j] \]

\(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]\) 有多少回文子串

转移

\[dp[i][j]=dp[i][j-1]+dp[i+1][j]-dp[i+1][j-1]+(s[i]==s[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\) 号珠子的区间所能得到的最大能 量,

转移:

就枚举最后一步聚合的位置(断点)即可

\[f[j][i] = max(f[j][i],f[j][k] + f[k + 1][i] + a[j]*a[k + 1]*a[i + 1]) \]

\[Ans = max(f[1][n],f[2][n + 1],f[3][n + 2]……f[n][2*n - 1]) \]

区间dp转移套路

一般 \(n=1000\) 的区间 \(dp\) 问题,由于状态就是二维的了,转移一般都是 \(O(1)\)\(dp\) 转移过程中主要考虑就是 \(L\)\(R\) 两个边界的情况,正如之前的2道题

\(n=100\) 的区间 \(dp\),除了边界往往还要枚举这个区间从哪个位置划分

posted @ 2021-02-05 21:02  Dita  阅读(169)  评论(4编辑  收藏  举报