洛谷 - P1880 - 石子合并
第一次接触区间DP,真奇妙.....
1 #include <iostream> 2 #define Maxsize 202 3 #define INF 0x3fffffff 4 using namespace std; 5 int dp[Maxsize][Maxsize]; 6 int dp2[Maxsize][Maxsize]; 7 int arr[Maxsize]; 8 int sum[Maxsize][Maxsize]; 9 int get_sum(int i,int j){ 10 if(sum[i][j] == 0){ 11 for(int t = i; t <= j; t++)sum[i][j] += arr[t]; 12 } 13 return sum[i][j]; 14 } 15 int main(){ 16 int num; 17 cin >> num; 18 for(int i = 1,j = num + 1; i <= num; i++,j++){ 19 cin >> arr[i]; arr[j] = arr[i]; 20 } 21 22 for(int length = 2; length <= num; length++){ // 枚举长度, 1 不用枚举了, 如果枚举长度为 1 , 找不到分界点 ( end = start + 1 - 1 = start; k = start = end ) 23 for(int start = 1; start <= num; start++){ // 枚举起点 24 int end = start + length - 1; // 确定终点 25 dp2[start][end] = INF; 26 for(int k = start; k < end; k++){ // 确定分界点 , k 一定要从 start , 否则对于 dp[k+1][end] 这种情况会漏掉第二个石子分向右边的情况 . 27 dp[start][end] = max(dp[start][end],dp[start][k] + dp[k+1][end] + get_sum(start,end)); 28 dp2[start][end] = min(dp2[start][end],dp2[start][k] + dp2[k+1][end] + get_sum(start,end)); 29 } 30 } 31 } 32 33 int ans = 0; 34 int ans2 = INF; 35 for(int i = 1; i <= num; i++){ 36 ans = max(ans,dp[i][i+num-1]); 37 ans2 = min(ans2,dp[i][i+num-1]); 38 } 39 cout<<ans2<<" "<<ans; 40 return 0; 41 }
主要谈一谈为什么这样遍历可以保证递推顺序正确:
由于以区间长度为主循环,以起点为次循环,故第一遍循环可以确定两堆石子合并的所有可能情况。这个很好理解。
第二遍循环,是在求解当区间长度为3时的所有情况。这个问题的求解需要利用区间长度为2的子问题的结果,而子问题再上一次被求解过了。
以 length = 3 , start = 1, end = 3, k = 2 为例:
求解这个状态的方程为: dp [1] [3] = max(dp[1] [3] , dp[1] [2] + dp[3] [3] + get_sum(1,3) ) ;
可以看到, dp[1] [2] 已经在 length = 1 时求解过了, 而dp[3] [3] 是 i = j ( length = 0 ) 的默认情况,为0;
同理我们看 length = 3 , start = 2, end = 4, k = 3 :
求解这个状态的方程为 : dp[2] [4] = max(dp[2] [4], dp[2] [3] + dp[4][4] + get_sum(1,3) ) ;
总结规律可以看出当我们枚举分界点k的时候,实际上就是把问题分段了,子问题的length 当然小于当前问题的 length,因此这个递推顺序是很正确的。
另外,当我们是在无法弄明白如何确定边界状态、递推顺序的时候,用记忆化递归也比较好。
#include <iostream> #define Maxsize 400 #define INF 10000 using namespace std; int arr[Maxsize]; int DP[Maxsize][Maxsize]; int DP2[Maxsize][Maxsize]; int sum[Maxsize][Maxsize]; int get_sum(int i,int j){ if(sum[i][j] == 0){ int z = 0; for(int p = i; p <= j; p++)z += arr[p]; sum[i][j] = z; } return sum[i][j]; } int dfs(int i,int j){ if(i == j)return 0; else if(DP[i][j] == 0){ for(int k = i ; k < j; k++){ DP[i][j] = max(DP[i][j],dfs(i,k)+dfs(k+1,j) + get_sum(i, j)); } } return DP[i][j]; } int dfs2(int i,int j){ if(i == j)return 0; else if(DP2[i][j])return DP2[i][j]; else{ int m = INF; for(int k = i; k < j; k++) m = min(m,dfs2(i, k)+dfs2(k+1, j)+get_sum(i, j)); DP2[i][j] = m; } return DP2[i][j]; } int main(){ int num; cin >> num; for(int i = 1,j = num + 1; i <= num; i++,j++){ cin >> arr[i]; arr[j] = arr[i]; } int ans = 0,ans2= INF; for(int i = 1,j = num; i < num; i++,j++){ ans = max(ans,dfs(i,j)); ans2 = min(ans2,dfs2(i,j)); } cout<<ans2<<endl<<ans; return 0; }
---- suffer now and live the rest of your life as a champion ----