LeetCode---区间DP

目录

简介

分析

例题:312. 戳气球

例题:1000. 合并石头的最低成本


简介

区间DP在leetcode上也有体现(主要是合并石子类型),本文就leetcode上出现的区间DP做一个总结。

题目原型:

5301 石子合并 0x50「动态规划」例题

描述

设有N堆沙子排成一排,其编号为1,2,3,…,N(N<=300)。每堆沙子有一定的数量,可以用一个整数来描述,现在要将这N堆沙子合并成为一堆,每次只能合并相邻的两堆,合并的代价为这两堆沙子的数量之和,合并后与这两堆沙子相邻的沙子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同,如有4堆沙子分别为 1  3  5  2 我们可以先合并1、2堆,代价为4,得到4 5 2 又合并 1,2堆,代价为9,得到9 2 ,再合并得到11,总代价为4+9+11=24,如果第二步是先合并2,3堆,则代价为7,得到4 7,最后一次合并代价为11,总代价为4+7+11=22;问题是:找出一种合理的方法,使总的代价最小。输出最小代价。

输入格式

第一行一个数N表示沙子的堆数N。
第二行N个数,表示每堆沙子的质量(<=1000)。

输出格式

合并的最小代价

样例输入

4
1 3 5 2

样例输出

22

分析

若最初的第 l 堆石子和第 r 堆石子被合并成一堆,则说明l~r之间的石子也已经被合并,只有这样,l和r才能相邻。因此可以在任意时刻,任意一堆石子均可以用一个闭区间[l, r]来描述,表示这堆石子是由最初的第l~r个石子合并而成的,其数量为SUM(A[I],A[R])。另外,一定存在一个整数K,在这对石子形成之前,先有第l~k堆石子被合并成一堆,第k+1~r堆石子被合并成一堆,然后这辆堆石子合并成一堆。

对应到动态规划中,就是一长度较小的区间上的信息向一个更长的区间发生了转移;因此我们可以根据长度len作为dp阶段,划分点K作为决策。

dp[l][r] = min (dp[l,k] + dp[k+1][r]) + sum(A[l],A[r])  (  l <= k < r )

dp[l][r] : 把最初的第l堆到第r堆石子合并成一堆,需要消费的最少体力。

using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define oo cout<<"!!!"<<endl;
typedef long long ll;
typedef unsigned long long ull;
#define ms(s) memset(s, 0, sizeof(s))
const int inf = 0x3f3f3f3f;
//head

const int maxn = 1e6+11;

int f[333][333],sum[333],a[333];
int main() 
{
	int n;
	memset(f,0x3f,sizeof f);
	cin>>n;
	rep(i,1,n+1)scanf("%d",a+i),sum[i] += sum[i-1] + a[i],f[i][i] = 0;//dp数组初始化,求前缀和

	for(int len = 2;len<=n;len++)
		for(int l = 1;l<=n-len+1;l++)
		{
			int r = l+len-1;
			for(int k = 1;k<r;k++)
				f[l][r] = min(f[l][r],f[l][k] +f[k+1][r]);
			f[l][r] += sum[r] - sum[l-1];
		}

    cout << f[1][n] << endl;

    return 0;
}

例题:312. 戳气球

有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。

求所能获得硬币的最大数量。

说明:

你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

分析:和合并石子问题很像,只是这个的决策是 区间中最后剩余的那个气球。

dp[l][r] = max(dp[l][r],dp[l][k-1] + dp[k+1][r]+ nums[l-1] * nums[r+1] * nums[k]);  ( l<=k<=r)

class Solution {
public:
    int maxCoins(vector<int>& a) {
      
        int n = a.size();
        if(!n)return 0;
        vector<int> nums(n+2,0);
        nums[0] = 1;
        nums[n+1] = 1;
        for(int i = 0;i<n;++i)nums[i+1] = a[i];
        //n+=2;
        vector< vector<int> > dp(n+11,vector<int>(n+11,0));
        for(int len = 1;len<=n;++len)
        {
            for(int l = 1;l <= n - len + 1;++l)
            {
                int r = l+len-1;
                for(int k = l;k<=r;++k)//最终剩余的气球
                    dp[l][r] = max(dp[l][r],dp[l][k-1] + dp[k+1][r]+ //k+1>r k-1<l都没事,dp数组为0
                                   nums[l-1] * nums[r+1] * nums[k]);
    
            }
        }
        return dp[1][n];
    }
};

 

例题:1000. 合并石头的最低成本

有 N 堆石头排成一排,第 i 堆中有 stones[i] 块石头。

每次移动(move)需要将连续的 K 堆石头合并为一堆,而这个移动的成本为这 K 堆石头的总数。

找出把所有石头合并成一堆的最低成本。如果不可能,返回 -1 。

这题加上了限制条件:每次合并是连续合并k堆,并不是合并两堆了;

当k=2时,我们其实是将一部分合并成1堆,另一部分合并成1堆。

现在K!=2了,我们可以考虑先将一部分合并成K-1堆,另外一部分合并成1堆,分界点为k,然后再合并。至于这里为什么要分出来1堆,而不是2,3,4?  因为1是更小的子集,并且初始化dp[i][i][0] = 0;

初始化 dp[i][i][1]=0

dp[i][j][k]=min(dp[i][j][k],dp[i][k][k-1]+dp[k+1][j][1])  

dp[i][j][1]=min(dp[i][j][K]+sum[j]-sum[k-1])  // K堆合并为1堆

class Solution {
public:
    int dp[33][33][33],sum[33];
    int mergeStones(vector<int>& stones, int K) {
        int  n = stones.size();
        if( (n-1) % (K-1) ) return -1;
        memset(dp,0x3f,sizeof (dp));
        sum[0] = 0;
        for(int i = 1;i<=n;++i)
        {
            dp[i][i][1] = 0;
            sum[i] = sum[i-1] + stones[i-1];
        }

        for(int len = 2;len<=n;++len)
            for(int l = 1;l<=n-len+1;++l)
            {
                int r = l + len - 1;
                for(int k = l;k<r;++k)//分界点
                {
                    for(int m = 2;m<=len;++m)//m堆
                    dp[l][r][m] = min(dp[l][r][m],dp[l][k][m-1] + dp[k+1][r][1]);
                }
                dp[l][r][1] = min(dp[l][r][1],dp[l][r][K] + sum[r] - sum[l-1]);//K堆 -> 1堆
            }
        return dp[1][n][1];
    }
};
发布了257 篇原创文章 · 获赞 36 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/sgh666666/article/details/105178376
今日推荐