[NOI1995] 돌멩이 합병 & [NOIP2006] 에너지 목걸이 문제풀이

28349 단어
[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
",__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
%d
", ansmin, ansmax); 61 return 0; 62 } 63 64 }//namespace chino 65 66 int main(){return chino::main();}

별문제
[NOIP2006] 에너지 목걸이
또 하나의 구간 dp 고전 문제
돌멩이랑 합치면 좀 비슷해요.
문제를 보자마자 네이버에서 돌을 합쳐서 고쳐서 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
",__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
", 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
",__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
", 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

좋은 웹페이지 즐겨찾기