区间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 }

 

posted @ 2020-04-09 18:31  dzcixy  阅读(140)  评论(0)    收藏  举报