[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 #include2 #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 #include2 #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 #include2 #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();}