洛谷 - 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;
}
posted @ 2020-01-02 16:48  popozyl  阅读(217)  评论(0编辑  收藏  举报