思路分析和相关例题
关于区间dp
区间类动态规划是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的哪些元素合并而来有很大的关系。令状态 f ( i , j ) f(i, j) f ( i , j ) 表示将下标位置 i i i 到 j j j 的所有元素合并能获得的价值的最大值,那么 f ( i , j ) = m a x { f ( i , k ) + f ( k + 1 , j ) + c o s t } , k ∈ ( i , j ) f(i,j)=max\{f(i,k)+f(k+1,j)+cost\},k\isin(i,j) f ( i , j ) = m a x { f ( i , k ) + f ( k + 1 , j ) + c o s t } , k ∈ ( i , j ) ,c o s t cost c o s t 为将这两组元素合并起来的代价。
题目描述 解题思路板书
状态表示 f ( i , j ) f(i,j) f ( i , j )
集合: 所有将 [ i , j ] [i,j] [ i , j ] 石子合并成一堆的方案的集合 属性: 方案的最小开销 k k k 具有一般性 ,且 k ∈ ( i , j ) k\isin(i,j) k ∈ ( i , j )
状态转移方程为 f ( i , j ) = m i n k ∈ ( i , j ) { f ( i , k ) + f ( k + 1 , j ) + c o s t } f(i,j)=min_{k\isin(i,j)}\{f(i,k)+f(k+1,j)+cost\} f ( i , j ) = m i n k ∈ ( i , j ) { f ( i , k ) + f ( k + 1 , j ) + c o s t }
其中 c o s t cost c o s t 为 i ∼ j i\sim j i ∼ j 的质量和,可用前缀和优化,即为 s [ j + 1 ] s[j+1] s [ j + 1 ] -s [ i ] s[i] s [ i ]
代码实现C++代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <algorithm> using namespace std;const int N = 310 ;int n;int s[N];int f[N][N];int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; i ++ ) scanf ("%d" , &s[i]); for (int i = 1 ; i <= n; i ++ ) s[i] += s[i - 1 ]; for (int len = 2 ; len <= n; len ++ ) for (int i = 1 ; i + len - 1 <= n; i ++ ) { int l = i, r = i + len - 1 ; f[l][r] = 1e8 ; for (int k = l; k < r; k ++ ) f[l][r] = min (f[l][r], f[l][k] + f[k + 1 ][r] + s[r] - s[l - 1 ]); } printf ("%d\n" , f[1 ][n]); return 0 ; } 作者:yxc 链接:https: 来源:AcWing 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
J a v a Java J a v a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import java.util.*;import java.io.*;class Main { static BufferedReader cin = new BufferedReader (new InputStreamReader (System.in)); static final int N = 310 ; static final int INF = 0x3f3f3f3f ; public static void main (String[] args) throws Exception { int n = Integer.parseInt(cin.readLine().split(" " )[0 ]); String[] strs = cin.readLine().split(" " ); int [] s = new int [N]; for (int i = 1 ; i <= n; ++i) { s[i] = Integer.parseInt(strs[i - 1 ]); s[i] += s[i - 1 ]; } int [][] dp = new int [N][N]; for (int len = 2 ; len <= n; ++len) { for (int i = 1 ; i + len - 1 <= n; ++i) { int left = i, right = left + len - 1 ; dp[left][right] = INF; for (int k = left; k < right; ++k) { dp[left][right] = Math.min(dp[left][right], dp[left][k] + dp[k + 1 ][right] + s[right] - s[left - 1 ]); } } } System.out.println(dp[1 ][n]); cin.close(); } }
题目描述给定一个布尔表达式和一个期望的布尔结果 result,布尔表达式由 0
(false)、1
(true)、&
(AND)、 |
(OR) 和 ^
(XOR) 符号组成。实现一个函数,算出有几种可使该表达式得出 result 值的括号方法。
示例
1 2 3 4 5 6 输入: s = "1^0|0|1", result = 0 输出: 2 解释: 两种可能的括号方法是 1^(0|(0|1)) 1^((0|0)|1)
思路分析表达式由真值(0、1)和布尔运算符构成,这里将问题拆分成子问题,用一个 n × n × 2 n\times n \times 2 n × n × 2 维的数组存储中间结果
其中,d p [ i ] [ j ] [ 0 / 1 ] dp[i][j][0/1] d p [ i ] [ j ] [ 0 / 1 ] 表示真值 s [ i ] s[i] s [ i ] 到 s [ j ] s[j] s [ j ] 之间的子表达式的运算结果为 0 0 0 或 1 1 1 的方案数量
针对每一个子问题:「求区间 s [ i , j ] s[i,j] s [ i , j ] 构成的表达式的运算结果」,在区间 [ i , j ] [i,j] [ i , j ] 上选取切分点,将子表达式分成左右两部分
根据真值表和乘法原理,将两部分(两个「子子」问题)的方案数相乘并累加进 d p [ i ] [ j ] [ ] dp[i][j][] d p [ i ] [ j ] [ ]
状态转移方程:d p [ i ] [ j ] = s u m k ∈ ( i , j ) { d p [ i ] [ k − 1 ] × d p [ k + 1 ] [ j ] } dp[i][j] = sum_{k\isin(i,j)}\{dp[i][k - 1] \times dp[k+1][j]\} d p [ i ] [ j ] = s u m k ∈ ( i , j ) { d p [ i ] [ k − 1 ] × d p [ k + 1 ] [ j ] }
真值表
1 1 1 代表 true
,0 0 0 代表 false
代码实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Solution { public int countEval (String s, int result) { char [] chs = s.toCharArray(); int n = chs.length; int [][][] dp = new int [n][n][2 ]; for (int i = 0 ; i < n; ++i) { if (chs[i] == '0' || chs[i] == '1' ) { dp[i][i][chs[i] - '0' ] = 1 ; } } for (int len = 2 ; len <= n; len += 2 ) { for (int i = 0 ; i < n - len + 1 ; i += 2 ) { int j = i + len; for (int k = i + 1 ; k <= j - 1 ; k += 2 ) { if (chs[k] == '&' ) { dp[i][j][1 ] += dp[i][k - 1 ][1 ] * dp[k + 1 ][j][1 ]; dp[i][j][0 ] += dp[i][k - 1 ][1 ] * dp[k + 1 ][j][0 ] + dp[i][k - 1 ][0 ] * dp[k + 1 ][j][1 ] + dp[i][k - 1 ][0 ] * dp[k + 1 ][j][0 ]; } if (chs[k] == '|' ) { dp[i][j][0 ] += dp[i][k - 1 ][0 ] * dp[k + 1 ][j][0 ]; dp[i][j][1 ] += dp[i][k - 1 ][1 ] * dp[k + 1 ][j][0 ] + dp[i][k - 1 ][0 ] * dp[k + 1 ][j][1 ] + dp[i][k - 1 ][1 ] * dp[k + 1 ][j][1 ]; } if (chs[k] == '^' ) { dp[i][j][1 ] += dp[i][k - 1 ][1 ] * dp[k + 1 ][j][0 ] + dp[i][k - 1 ][0 ] * dp[k + 1 ][j][1 ]; dp[i][j][0 ] += dp[i][k - 1 ][1 ] * dp[k + 1 ][j][1 ] + dp[i][k - 1 ][0 ] * dp[k + 1 ][j][0 ]; } } } } return dp[0 ][n - 1 ][result]; } }
其他思路也可以采用分治的思路,枚举切分点,递归求出左右两部分的方案数量,再根据真值表和乘法原理处理结果。
可以借鉴记忆化搜索的思想,保存中间计算结果,用空间换时间。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Solution { public int countEval (String s, int result) { char [] chs = s.toCharArray(); int n = chs.length; int [][][] cache = new int [n][n][]; int [] res = dfs(chs, 0 , n - 1 , cache); return result == 0 ? res[0 ] : res[1 ]; } public int [] dfs(char [] s, int left, int right, int [][][] cache) { int [] res = new int [2 ]; if (left >= right) { res[s[left] - '0' ] = 1 ; return res; } if (cache[left][right] != null ) return cache[left][right]; for (int i = left; i <= right; ++i) { if (s[i] == '0' || s[i] == '1' ) continue ; int [] leftRes = dfs(s, left, i - 1 , cache); int [] rightRes = dfs(s, i + 1 , right, cache); if (s[i] == '&' ) { res[1 ] += leftRes[1 ] * rightRes[1 ]; res[0 ] += leftRes[1 ] * rightRes[0 ] + leftRes[0 ] * rightRes[0 ] + leftRes[0 ] * rightRes[1 ]; } if (s[i] == '|' ) { res[0 ] += leftRes[0 ] * rightRes[0 ]; res[1 ] += leftRes[1 ] * rightRes[0 ] + leftRes[1 ] * rightRes[1 ] + leftRes[0 ] * rightRes[1 ]; } if (s[i] == '^' ) { res[1 ] += leftRes[0 ] * rightRes[1 ] + leftRes[1 ] * rightRes[0 ]; res[0 ] += leftRes[0 ] * rightRes[0 ] + leftRes[1 ] * rightRes[1 ]; } } cache[left][right] = res; return res; } }
其他例题 题目描述你有一个凸的 n
边形,其每个顶点都有一个整数值。给定一个整数数组 values
,其中 values[i]
是第 i
个顶点的值(即 顺时针顺序 )。
假设将多边形 剖分 为 n - 2
个三角形。对于每个三角形,该三角形的值是顶点标记的乘积 ,三角剖分的分数是进行三角剖分后所有 n - 2
个三角形的值之和。
返回 多边形进行三角剖分后可以得到的最低分 。
示例
1 2 3 输入:values = [1,3,1,4,1,5] 输出:13 解释:最低分数三角剖分的得分情况为 1*1*3 + 1*1*4 + 1*1*5 + 1*1*1 = 13。
简要分析多边形顶点依次编号为 1,2,3,...,n-1
,记 d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 表示区间 [ i , j ] [i,j] [ i , j ] 构成的多边形的 最低分
区间DP做法,先枚举区间长度 l e n len l e n ,再枚举左侧起点 i i i ,最后在当前区间内 [ i , i + l e n − 1 ] [i, i + len-1] [ i , i + l e n − 1 ] ,枚举切割点 k k k
考虑一般情况 如下图所示
切割点 k k k 将 [ i , j ] [i,j] [ i , j ] 分成三个多边形,其中 S 0 S_0 S 0 的得分为 values[i] * values[j] * values[k]
S 1 S_1 S 1 的得分为 d p [ i ] [ k ] dp[i][k] d p [ i ] [ k ] ,S 2 S_2 S 2 的得分为 d p [ k ] [ j ] dp[k][j] d p [ k ] [ j ] ,则区间的总得分为 d p [ i ] [ k ] + S c o r e ( S 0 ) + d p [ k ] [ j ] dp[i][k]+Score(S_0)+dp[k][j] d p [ i ] [ k ] + S c o r e ( S 0 ) + d p [ k ] [ j ]
枚举当前区间的所有的切割点,求出最小得分即可
代码实现1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { static final int INF = 0x3f3f3f3f ; public int minScoreTriangulation (int [] values) { int n = values.length; int [][] dp = new int [n][n]; for (int len = 3 ; len <= n; ++len) { for (int i = 0 ; i < n - len + 1 ; ++i) { int l = i, r = i + len - 1 ; dp[l][r] = INF; for (int k = l + 1 ; k < r; ++k) { dp[l][r] = Math.min(dp[l][r], dp[l][k] + values[l] * values[r] * values[k] + dp[k][r]); } } } return dp[0 ][n - 1 ]; } }