石子合并--区间dp典例

题目
题意给排在一排的n堆石子将其合并
输出其最小价值

输入 :
4
1 3 5 2
输出:
22

分析

第一阶段:

dp[1][1],dp[2][2],dp[3][3],dp[4][4]

因为一开始还没有合并,所以这些值应该全部为0。

第二阶段:
两两合并过程如下,其中sum(i,j)表示石头的数量,即从i开始数j个数的和

dp[1,2]=dp[1,1]+dp[2,2]+sum[1,2];
dp[2,3]=dp[2,2]+dp[3,3]+sum[2,3];
dp[3,4]=dp[3,3]+dp[4,4]+sum[4,4];

第三阶段:
三三合并可以拆成两两合并,拆分方法有两种,前两个为一组或后两个为一组

dp[1,3]=dp[1,2]+dp[3,3]+sum[1,3]或dp[1,3]=dp[1,1]+dp[2,3]+sum[1,3];取其最优

dp[2,4]=dp[2,2]+dp[3,4]+sun[2,4]或
dp[2,4]=dp[2,3]+dp[3,3]+sum[2,4];取其最优

第四阶段:
四四合并的拆分方法用三种,同理求出三种分法的得分,取其最优即可。以后第五阶段、第六阶段依次类推,最后在第六阶段中找出最优答案即可。

l和r的差距len=1
***********************************
l的位置l=1所以r的位置r=2
k的位置k=1
划分的区间[1,1] [2,2]
dp[1][2]=min(dp[1][2]=2147483647,dp[1][1]=0+dp[2][2]=0+sum=4)=4

l的位置l=2所以r的位置r=3
k的位置k=2
划分的区间[2,2] [3,3]
dp[2][3]=min(dp[2][3]=2147483647,dp[2][2]=0+dp[3][3]=0+sum=8)=8

l的位置l=3所以r的位置r=4
k的位置k=3
划分的区间[3,3] [4,4]
dp[3][4]=min(dp[3][4]=2147483647,dp[3][3]=0+dp[4][4]=0+sum=7)=7

l和r的差距len=2
***********************************
l的位置l=1所以r的位置r=3
k的位置k=1
划分的区间[1,1] [2,3]
dp[1][3]=min(dp[1][3]=2147483647,dp[1][1]=0+dp[2][3]=8+sum=9)=17

k的位置k=2
划分的区间[1,2] [3,3]
dp[1][3]=min(dp[1][3]=17,dp[1][2]=4+dp[3][3]=0+sum=9)=13

l的位置l=2所以r的位置r=4
k的位置k=2
划分的区间[2,2] [3,4]
dp[2][4]=min(dp[2][4]=2147483647,dp[2][2]=0+dp[3][4]=7+sum=10)=17

k的位置k=3
划分的区间[2,3] [4,4]
dp[2][4]=min(dp[2][4]=17,dp[2][3]=8+dp[4][4]=0+sum=10)=17

l和r的差距len=3
***********************************
l的位置l=1所以r的位置r=4
k的位置k=1
划分的区间[1,1] [2,4]
dp[1][4]=min(dp[1][4]=2147483647,dp[1][1]=0+dp[2][4]=17+sum=11)=28

k的位置k=2
划分的区间[1,2] [3,4]
dp[1][4]=min(dp[1][4]=28,dp[1][2]=4+dp[3][4]=7+sum=11)=22

k的位置k=3
划分的区间[1,3] [4,4]
dp[1][4]=min(dp[1][4]=22,dp[1][3]=13+dp[4][4]=0+sum=11)=22
	从前往后计算--》小区间先大区间划分
    //for (ll len = 1; len < n; len++) {//len代表l和r的下表的差值
    //    cout << "l和r的差距len=" << len << "\n***********************************\n";
    //    for (ll l = 1; l + len <= n; l++) {//从第l堆开始
    //        ll r = l + len;//到第r堆结束
    //        cout << "l的位置l=" << l <<"所以r的位置r="<<r<< "\n";
    //        dp[l][r] = INF;//求的是最小,先设其为INF
    //        for (ll k = l; k < r; k++) {//l和r之间用k分割
    //            cout << "k的位置k=" << k << "\n";
    //            cout << "划分的区间" << "[" << l << "," << k << "]\t" << "[" << k + 1 << "," << r << "]\n";
    //            
    //            cout << "dp[" << l << "]" << "[" << r << "]=" << "min(dp[" << l << "]" << "[" << r << "]=" <<dp[l][r] << "," << "dp[" << l << "]" << "[" << k << "]=" << dp[l][k] << "+dp[" << k + 1 << "]" << "[" << r << "]=" << dp[k + 1][r] << "+sum=" << prefix[r] - prefix[l - 1] << ")=";
    //            dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r] + prefix[r] - prefix[l - 1]);
    //            cout << dp[l][r] << "\n\n";
    //        }
    //    }
    //}

动态方程为dp[l][r]=dp[l][k]+dp[k+1][r]+sum[l][r];

参考代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e3 + 5;
const ll MOD = 1e9 + 7;
const ll INF = 0x7fffffff;
ll n;
ll prefix[maxn];
ll dp[maxn][maxn];
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (ll i = 1; i <= n; i++) {
        ll t;
        cin >> t;
        prefix[i] = prefix[i - 1] + t;//求前缀和
    }
    for (ll len = 1; len < n; len++) {//len代表l和r的下表的差值
        for (ll l = 1; l + len <= n; l++) {//从第l堆开始
            ll r = l + len;//到第r堆结束
            dp[l][r] = INF;//求的是最小,先设其为INF
            for (ll k = l; k < r; k++) {//l和r之间用k分割
                dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r] + prefix[r] - prefix[l - 1]);
            }
        }
    }
    cout << dp[1][n];
    return 0;
}

题目二
题意:一个环中有n堆石子将其合并
输出其最小和最大价值

输入
4
4 5 9 4
输出
43
54

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e3 + 5;
const ll MOD = 1e9 + 7;
const ll INF = 0x7fffffff;
ll n;
ll w[maxn];
ll maxv[maxn][maxn],minv[maxn][maxn];
ll perfix[maxn];
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    ll n;
    cin >> n;
    for (ll i = 1; i <= n; i++) {
        cin >> w[i];
        w[n + i] = w[i];
    }

    for (ll i = 1; i <= n * 2; i++) perfix[i] = perfix[i - 1] + w[i];

    for (ll len = 1; len < n; len++) {//最长的区间长度为n,差值取n-1
        for (ll l = 1; l + len <= 2 * n; l++) {//区间起点l
            ll r = l + len ;//则区间终点r实际取不到2*n,就可满足从首开始又取到首
            minv[l][r] = INF;
            for (ll k = l; k < r; k++) {
                minv[l][r] = min(minv[l][r], minv[l][k] + minv[k + 1][r] + perfix[r] - perfix[l - 1]);
                maxv[l][r] = max(maxv[l][r], maxv[l][k] + maxv[k + 1][r] + perfix[r] - perfix[l - 1]);
            }
        }
    }

    ll min_ans = INF;
    ll max_ans = 0;
    for (ll i = 1; i <= n; i++) {
        min_ans = min(min_ans, minv[i][i + n-1]);
        max_ans = max(max_ans, maxv[i][i + n-1]);
    }
    cout << min_ans << "\n" << max_ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值