区间dp
acwing 282.石子合并
https://www.acwing.com/problem/content/284/

①因为我们枚举的划分线是左边部分包含该划分点,所以划分点$k$是可以等于$i$的,但是不包含$j$,所以划分点$k$不能等于$j$。
②为了使在计算当前状态的时候,所需要的状态已经都计算出了,所以我们要以区间的长度进行枚举(枚举区间长度应从$2$开始枚举,因为区间长度是$1$的时候,合并没有代价)。
③因为合并的代价都是区间的和与区间的和相加,而每一次求区间和的复杂度太高,所以我们需要预先处理一个前缀和数组$s$。
代码:$O(n^3)$
1 #include <iostream> 2 #include <algorithm> 3 4 using namespace std; 5 6 const int N = 310; 7 int f[N][N]; 8 int s[N]; 9 int n; 10 11 int main(){ 12 cin >> n; 13 for(int i = 1 ; i <= n ; i ++)cin >> s[i], s[i] += s[i - 1]; 14 15 for(int len = 2 ; len <= n ; len ++) 16 for(int i = 1 ; i + len - 1 <= n ; i ++) 17 { 18 int l = i, r = i + len - 1; 19 f[l][r] = 1e8; 20 for(int k = l ; k < r ; k ++) 21 f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]); 22 } 23 24 cout << f[1][n] << endl; 25 return 0; 26 }
acwing 1068.环形石子合并
https://www.acwing.com/problem/content/1070/
题意:在石子合并的基础上,改成环形的形式(将首尾相连,首和尾算相邻)。
核心:将环转换为区间。
朴素的想法是:将每两个点相邻关系看做是有一条边,枚举每一条边,“砍断”这条边,得到一条线性的石子合并问题求解。这样的时间复杂度就是$O(n^4)$,会超时。
优化:将两条一样的区间相连。这样,得到长度为$2n$的区间上,包含了所有$n$的区间。时间复杂度$O((2n)^3)$。

1 #include <iostream> 2 #include <algorithm> 3 #include <cstring> 4 5 using namespace std; 6 7 const int N = 410, INF = 0x3f3f3f3f;//开两倍空间 8 int f[N][N], g[N][N]; 9 int w[N], s[N]; 10 int n; 11 12 int main(){ 13 cin >> n; 14 for(int i = 1 ; i <= n ; i ++) 15 { 16 cin >> w[i]; 17 w[i + n] = w[i]; 18 } 19 for(int i = 1 ; i <= 2 * n ; i ++)s[i] = s[i - 1] + w[i]; 20 21 memset(f, 0x3f, sizeof f); 22 memset(g, -0x3f, sizeof g); 23 24 for(int len = 1 ; len <= n ; len ++) 25 for(int l = 1 ; l + len - 1 <= 2 * n ; l ++) 26 { 27 int r = l + len - 1; 28 if(len == 1)f[l][r] = g[l][r] = 0;//代价为0 29 for(int k = l ; k < r ; k ++) 30 { 31 f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]); 32 g[l][r] = max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]); 33 } 34 } 35 36 37 int minv = INF, maxv = -INF; 38 for(int i = 1 ; i <= n ; i ++) 39 { 40 minv = min(minv, f[i][i + n - 1]); 41 maxv = max(maxv, g[i][i + n - 1]); 42 } 43 44 cout << minv << endl; 45 cout << maxv << endl; 46 return 0; 47 }
acwing 320.能量项链
https://www.acwing.com/problem/content/322/
本题的形式是二维的$(2, 4)(4, 5)(5, 7)(7, 9)$,可以将其转成一维表示$(2, 4, 5, 7, 9)$,这样$(2, 4)$是一个矩阵,$(4, 5)$是一个矩阵,有三个数,所以区间长度要从$3$开始枚举。并且划分的依据$k$是从$l + 1$到$r - 1$的(左边至少要有一个矩阵或者右边至少要有一个矩阵)。
所以该题与前面不同的是,该题是以点作为相邻的依据,断开的也是点。环形石子合并是以边为相邻依据,断开的是边。
这样得到状态转移方程是:$f[l][r] = max(f[l][r], f[l][k] + f[k][r] + w[l] * w[k] * w[r])$
1 #include <iostream> 2 3 using namespace std; 4 5 const int N = 210; 6 7 int w[N],f[N][N]; 8 9 int main(){ 10 int n ; 11 cin>>n; 12 //展开两条链 13 for(int i = 1; i <= n ; i ++) 14 { 15 cin >> w[i]; 16 w[i + n] = w[i]; 17 } 18 19 for(int len = 3; len <= n + 1 ; len ++)//区间从3开始才有代价合并,并且长度是n + 1 20 //因为n个区间有n + 1个点,并且该题以点为划分依据 21 for(int l = 1 ; l + len - 1 <= 2 * n; l ++) 22 { 23 int r = l + len - 1; 24 for(int k = l + 1 ; k < r; k ++)//枚举分界线是从l + 1 到 r - 1 25 f[l][r] = max(f[l][r], f[l][k] + f[k][r] + w[l] * w[k] * w[r]); 26 } 27 //遍历长度为n + 1的区间 28 int res = 0; 29 for(int i = 1 ; i <= n ; i ++)res = max(res, f[i][i + n]); 30 cout << res << endl; 31 return 0; 32 }
acwing 1069.凸多边形的划分
https://www.acwing.com/problem/content/1071/
这题的状态转移方程和能量项链是一样的。但有些不同。

①这题以红色三角形的底边作为划分依据,将多边形划分为左边,红色三角形和右边三个部分,每个部分互不相关。其实就是以红色三角形作为分界线一样的区间$dp$。
需要注意的是,虽然是以边为划分,且是一个环,但实质上并不是一个环形的$dp$。例如$A$图中以$(1, 6)$划分,$B$图中以$(2, 1)$划分,实质上$A$图中的所有情况是包含$B$图中的,同理$B$图中的所有情况也是包含$A$图中的情况的,所以以边划分并不是多出了$n$倍的情况,他们实质都是同一种情况。所以这是一个区间长度为$n$的区间$dp$问题(能量项链是$2n$的环形区间$dp$)。
②分界点$k$的枚举依旧是$(l+1, r - 1)$,例如,当$(1, 6)$为底的时候,$k$必须是$(2, 5)$中的数字才能组成一个三角形。所以区间长度也应该从$3$开始枚举。
③另外该题的数据最大是$10^9$规模,单一存储不会爆$int$,但是三个数相乘会爆$long \ long$, 所以是要写高精度的。
1 #include <iostream> 2 #include <algorithm> 3 #include <cstring> 4 5 using namespace std; 6 7 typedef long long LL; 8 9 const int N = 51, M = 28; 10 11 LL f[N][N][M]; 12 int w[N]; 13 int n; 14 15 void add(LL a[], LL b[]) 16 { 17 static LL c[M]; 18 memset(c, 0, sizeof c); 19 for(int i = 0, t = 0; i < M ; i ++) 20 { 21 t += a[i] + b[i]; 22 c[i] = t % 10; 23 t /= 10; 24 } 25 memcpy(a, c, sizeof c); 26 } 27 28 void mul(LL a[], LL b) 29 { 30 static LL c[M]; 31 memset(c, 0, sizeof c); 32 LL t = 0; 33 for (int i = 0; i < M; i ++ ) 34 { 35 t += a[i] * b; 36 c[i] = t % 10; 37 t /= 10; 38 } 39 memcpy(a, c, sizeof c); 40 } 41 42 int cmp(LL a[], LL b[]) 43 { 44 for (int i = M - 1; i >= 0; i -- ) 45 if (a[i] > b[i]) return 1; 46 else if (a[i] < b[i]) return -1; 47 return 0; 48 } 49 50 void print(LL a[]) 51 { 52 int k = M - 1; 53 while (k && !a[k]) k -- ;//删除前导零 54 while (k >= 0) cout << a[k -- ]; 55 cout << endl; 56 } 57 58 int main(){ 59 cin >> n; 60 for(int i = 1 ; i <= n ; i ++)cin >> w[i]; 61 62 LL temp[M]; 63 for(int len = 3 ; len <= n ; len ++) 64 for(int l = 1 ; l + len - 1 <= n ; l ++) 65 { 66 int r = l + len - 1; 67 f[l][r][M - 1] = 9;//置为无穷 68 for (int k = l + 1; k < r; k ++ ) 69 { 70 memset(temp, 0, sizeof temp);//注意清空 71 temp[0] = w[l]; 72 mul(temp, w[k]); 73 mul(temp, w[r]); 74 add(temp, f[l][k]); 75 add(temp, f[k][r]); 76 if (cmp(f[l][r], temp) > 0) 77 memcpy(f[l][r], temp, sizeof temp); 78 } 79 } 80 print(f[1][n]); 81 return 0; 82 }
acwing 479.加分二叉树
https://www.acwing.com/problem/content/481/

再用一个$path$数组存储一下改区间的根节点,用$dfs$跑一遍。输出前序遍历。
1 #include <iostream> 2 #include <algorithm> 3 4 using namespace std; 5 6 const int N = 31; 7 int f[N][N], path[N][N]; 8 int w[N]; 9 int n; 10 11 void dfs(int l, int r) 12 { 13 if(l > r)return; 14 int k = path[l][r]; 15 cout << k << " "; 16 dfs(l, k - 1); 17 dfs(k + 1, r); 18 } 19 20 int main(){ 21 cin >> n; 22 for(int i = 1 ; i <= n ; i ++)cin >> w[i]; 23 24 for(int len = 1 ; len <= n ; len ++) 25 for(int l = 1 ; l + len - 1 <= n ; l ++) 26 { 27 int r = l + len - 1; 28 for(int k = l ; k <= r ; k ++) 29 { 30 int left = k == l ? 1 : f[l][k - 1]; 31 int right = k == r ? 1 : f[k + 1][r]; 32 int score = left * right + w[k]; 33 if(l == r)score = w[r]; 34 if(f[l][r] < score) 35 { 36 f[l][r] = score; 37 path[l][r] = k; 38 } 39 } 40 } 41 42 cout << f[1][n] << endl; 43 dfs(1, n); 44 return 0; 45 }
acwing 321.棋盘分割
https://www.acwing.com/problem/content/323/
二维前缀和+记忆化搜索
$\frac{1}{n}(\sum_{i=1}^{n}(x_i-\bar{x}))=\frac{1}{n}(\sum_{i=1}^{n}((x_i)^2 - 2\bar{x}\sum_{i = 1}^{n}x_i+n(\bar{x})^2 ))=\frac{1}{n}(\sum_{i=1}^{n}((x_i)^2 -n(\bar{x})^2 ))$
1 #include <iostream> 2 #include <algorithm> 3 #include <cmath> 4 #include <cstring> 5 6 using namespace std; 7 8 const int N = 16; 9 const double INF = 1e9; 10 double f[N][N][N][N][N], s[N][N]; 11 int n; 12 double X; 13 14 double get(int x1, int y1, int x2, int y2) 15 { 16 double sum = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1] - X; 17 return sum * sum / n; 18 } 19 20 double dp(int x1, int y1, int x2, int y2, int k) 21 { 22 double &v = f[x1][y1][x2][y2][k]; 23 if(v >= 0)return v; 24 if(k == 1)return v = get(x1, y1, x2, y2); 25 26 v = INF; 27 for(int i = x1 ; i < x2 ; i ++)//按行切 28 { 29 v = min(v, dp(x1, y1, i, y2, k - 1) + get(i + 1, y1, x2, y2)); 30 v = min(v, dp(i + 1, y1, x2, y2, k - 1) + get(x1, y1, i, y2)); 31 } 32 for(int i = y1 ; i < y2 ; i ++)//按列切 33 { 34 v = min(v, dp(x1, y1, x2, i, k - 1) + get(x1, i + 1, x2, y2)); 35 v = min(v, dp(x1, i + 1, x2, y2, k - 1) + get(x1, y1, x2, i)); 36 } 37 return v; 38 } 39 40 int main(){ 41 cin >> n; 42 for(int i = 1 ; i <= 8 ; i ++) 43 for(int j = 1 ; j <= 8 ; j ++) 44 { 45 cin >> s[i][j]; 46 s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]; 47 } 48 49 memset(f, -1, sizeof f); 50 X = (double)s[8][8] / n;//浮点数除法 51 printf("%.3lf\n", sqrt(dp(1, 1, 8, 8, n))); 52 return 0; 53 }

浙公网安备 33010602011771号