[NOI1995]石子合并 & [NOIP2006]能量项链 题解

[NOI1995]石子合并

区间dp经典题 , 自己鸽了好久才做

显然这道题不同于 果子合并 那道题


我们设 dp[i][j] 表示从 i 到 j 这个区域内的最大/最小价值

然后就很显然 , 我们要从 i 到 j 中选一个断点 k , 然后(以max为例)

 1 dp[i][j] = std::max (dp[i][j] , dp[i][k] + dp[k + 1][j] + sum(i, j)); 

其中sum(i,j)是 i 到 j 的石子总数(前缀和可做)

那怎么枚举呐?

1 for(int i = 1; i <= n; i++){
2     for(int j = 1; j <= n; j++){
3         for(int k = i; k < j; k++){
4             dp[i][j] = std::max (dp[i][j] , dp[i][k] + dp[k + 1][j] + sum(i, j));
5         }
6     }  
7 }

这样?
Too Simple!

我们很容易发现一个问题 , 我们可能需要的 dp[k + 1][j] 是初始值而非这段区间的答案(k + 1 != j , 如果等于的话本来就应该是初始值)

那我们换一种思路 , 我们枚举 (j - i) 这个差值

那不就保证了转移所需的状态都存在了嘛


 

另外 , 这道题是个环 , 所以我们需要破环为链 , 具体做法看代码

 1 #include
 2 #include
 3 #include
 4 #include
 5 #include
 6 #include
 7 #define APART puts("----------------------")
 8 #define debug 1
 9 #define FILETEST 1
10 #define inf 310
11 #define ll long long
12 #define ha 998244353
13 #define INF 0x7fffffff
14 #define INF_T 9223372036854775807
15 #define DEBUG printf("%s %d\n",__FUNCTION__,__LINE__)
16 
17 namespace chino{
18 
19 inline void setting(){
20 #if FILETEST
21     freopen("_test.in", "r", stdin);
22     freopen("_test.me.out", "w", stdout);
23 #endif
24     return;
25 }
26 
27 inline int read(){
28     char c = getchar(), up = c; int num = 0;
29     for(; c < '0' || c > '9'; up = c, c = getchar());
30     for(; c >= '0' && c <= '9'; num = (num << 3) + (num << 1) + (c ^ '0'), c = getchar());
31     return  up == '-' ? -num : num;
32 }
33 
34 int n;
35 int num[inf];
36 int sum[inf];
37 int dpmin[inf][inf];
38 int dpmax[inf][inf];
39 
40 inline int main(){
41     n = read();
42     for(int i = 1; i <= n; i++)
43         num[i + n] = num[i] = read();
44     for(int i = 1; i <= (n << 1); i++)
45         sum[i] = sum[i - 1] + num[i];
46     for(int c = 1; c < n; c++){
47         for(int i = 1, j = 1 + c; i < (n << 1) && j < (n << 1); i++, j = i + c){
48             dpmin[i][j] = INF >> 1;
49             for(int k = i; k < j; k++){
50                 dpmin[i][j] = std::min (dpmin[i][j], dpmin[i][k] + dpmin[k + 1][j] + sum[j] - sum[i - 1]);
51                 dpmax[i][j] = std::max (dpmax[i][j], dpmax[i][k] + dpmax[k + 1][j] + sum[j] - sum[i - 1]);
52             }
53         }
54     }
55     int ansmin = INF, ansmax = -INF;
56     for(int i = 1; i <= n; i++){
57         ansmin = std::min (ansmin, dpmin[i][i + n - 1]);
58         ansmax = std::max (ansmax, dpmax[i][i + n - 1]);
59     }
60     printf("%d\n%d\n", ansmin, ansmax);
61     return 0;
62 }
63 
64 }//namespace chino
65 
66 int main(){return chino::main();}

另一道题

[NOIP2006]能量项链

又一道区间dp经典题

和石子合并有点相似

一看到题我就naive的写了一个 石子合并·改 然后拿 10pts 滚了

做法无非是枚举所有区间 (i, j) , 然后枚举断点 k , 区间 (i, j) 答案由 (i, k) 和 (k + 1, j) 转移得来

10pts:

 1 #include
 2 #include
 3 #include
 4 #include
 5 #include
 6 #include
 7 #define APART puts("----------------------")
 8 #define debug 1
 9 #define FILETEST 1
10 #define inf 1010
11 #define ll long long
12 #define ha 998244353
13 #define INF 0x7fffffff
14 #define INF_T 9223372036854775807
15 #define DEBUG printf("%s %d\n",__FUNCTION__,__LINE__)
16 
17 namespace chino{
18 
19 inline void setting(){
20 #if FILETEST
21     freopen("_test.in", "r", stdin);
22     freopen("_test.me.out", "w", stdout);
23 #endif
24     return;
25 }
26 
27 inline int read(){
28     char c = getchar(), up = c; int num = 0;
29     for(; c < '0' || c > '9'; up = c, c = getchar());
30     for(; c >= '0' && c <= '9'; num = (num << 3) + (num << 1) + (c ^ '0'), c = getchar());
31     return  up == '-' ? -num : num;
32 }
33 
34 int n;
35 int ans;
36 int a[inf];
37 int dp[inf][inf];
38 
39 inline int main(){
40     n = read();
41     for(int i = 1; i <= n; i++)
42         a[i + n] = a[i] = read();
43     n <<= 1;
44     for(int i = 1; i <= n; i++){
45         for(int j = i + 1; j <= n && j - i < (n >> 1); j++){
46             if(j - i == 1)
47                 dp[i][j] = a[i] * a[j] * a[j + 1];
48             for(int k = i + 1; k < j; k++)
49                 dp[i][j] = std::max (dp[i][j] , dp[i][k] + dp[k + 1][j] + a[i] * a[k + 1] * a[j + 1]);
50             ans = std::max (ans, dp[i][j]);
51         }
52     }
53     printf("%d\n", ans);
54     return 0;
55 }
56 
57 }//namespace chino
58 
59 int main(){return chino::main();}

出错的原因也很简单 , 一个是没有控制好 i,j,k 的范围 , 而且最重要的是转移的时候 dp[k + 1][j] 会没有被转移到过

那么怎么避免呐

我们这个方法是取左端点 i  , 然后找右端点 j , 再找断点 k 

那我们反过来 , 先枚举右端点 i , 再枚举左端点 j ,再找断点 k

这样 , 也就保证了每次转移的时候都是没有问题的

 1 #include
 2 #include
 3 #include
 4 #include
 5 #include
 6 #include
 7 #define APART puts("----------------------")
 8 #define debug 1
 9 #define FILETEST 1
10 #define inf 1010
11 #define ll long long
12 #define ha 998244353
13 #define INF 0x7fffffff
14 #define INF_T 9223372036854775807
15 #define DEBUG printf("%s %d\n",__FUNCTION__,__LINE__)
16 
17 namespace chino{
18 
19 inline void setting(){
20 #if FILETEST
21     freopen("_test.in", "r", stdin);
22     freopen("_test.me.out", "w", stdout);
23 #endif
24     return;
25 }
26 
27 inline int read(){
28     char c = getchar(), up = c; int num = 0;
29     for(; c < '0' || c > '9'; up = c, c = getchar());
30     for(; c >= '0' && c <= '9'; num = (num << 3) + (num << 1) + (c ^ '0'), c = getchar());
31     return  up == '-' ? -num : num;
32 }
33 
34 int n;
35 int ans;
36 int a[inf];
37 int dp[inf][inf];
38 
39 inline bool check(int i, int j){
40     return (i - j) < n && j;
41 }
42 
43 inline int main(){
44     n = read();
45     for(int i = 1; i <= n; i++)
46         a[i + n] = a[i] = read();
47     int nn = n << 1;
48     for(int i = 2; i < nn; i++){
49         for(int j = i - 1; check(i, j); j--){
50             for(int k = j; k < i; k++)
51                 dp[i][j] = std::max (dp[i][j], dp[i][k + 1] + dp[k][j] + a[i + 1] * a[k + 1] * a[j]);
52             ans = std::max (ans, dp[i][j]);
53         }
54     }
55     printf("%d\n", ans);
56     return 0;
57 }
58 
59 }//namespace chino
60 
61 int main(){return chino::main();}

 

转载于:https://www.cnblogs.com/chiarochinoful/p/problem-noi-1995-shizihebing.html

你可能感兴趣的:([NOI1995]石子合并 & [NOIP2006]能量项链 题解)