区间DP入门

今天学长给我们讲了区间dp,当然听得云里雾里,讲完之后基本处于自闭状态,然后还是自己到大佬的博客,然后看博客,但是并没有找到很详细的博客,所以我想自己写一写,大神们勿喷哈.

一 .定义

区间DP,顾名思义是在区间上DP,它的主要思想就是先在小区间进行DP得到最优解,然后再利用小区间的最优解合并求大区间的最优解。

一般代码格式

//一般区间DP实现代码  

规定 dp[i][j] 为区间[i,j] 上的最优解

for (int i = 1; i <= n; i++                    //区间长度为1的初始化

    dp[i][i] = x;                                 // x视情况而定

for (int len = 2; len <= n; len++)              //枚举区间长度

{

    for (int i = 1, j = len; j <= n; i++, j++)  //区间[i,j]

    {

        //DP方程实现

    }

}

1. 石子合并

有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

引入区间dp

区间dp的思想是 利用一次次的合并过程去更新所得的结果,以便下一次合并使用。

从小的区间一点一点分析

设 A[i] 为第i堆的石子数目

1.若有一堆石子,无需合并 花费为 0

2.若有两堆石子,将这两堆合并  花费为A[0] + A[1]

3.若有三堆石子,有两种情况  

  •  先合并A[0],A[1]  再与A[2] 合并
  •  先合并A[1] A[2]  再与A[0] 合并

4.若有四堆石子,有三种情况  ……

分析一下算法过程   

dp 二维数组 ,横坐标代表区间起点,纵坐标代表区间终点 . 比如: dp[1][2] 代表 第一堆石子到第二堆石子的最优解,下面详细说吧,

可能这样提前说不太理解.

首先 最外层循环 控制石子堆的长度(len = 2 to n)  

for(int len = 2;len<=n;++len)

第二层循环,确定区间起点与终点

           for(int i=1,j=len;j<=n;++i,++j)
经过分析还需要一层循环,枚举将区间分割的那个点k

              for(int k = i;k<j;++k)

最后来看这个动态转移方程

dp[i][j] : 区间[i,j]上的石子合并最小花费

sum[i][j]: 区间[i,j] 的和

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

 

动态转移方程可以这么理解  现在已经将第 i 堆到第k堆 、 第k+1堆到第j堆合并了,现在只剩下这两堆  要将这两堆去合并

那么就是利用之前算出来的dp[i][k] + dp[k+1][j] 再加上区间[i,j]的和

为什么是加区间[i,j]的和呢   你把[i,k] 和[k+1,j]的石子合并到了一起

合并这两个区间的花费一定是这两个区间的总石子数,当然也就是[i,j]总石子数。

过程 :

1   初始化 dp数组

过程 :

 

整个过程就这样的 ,不断的更新小区间最优解,然后最总就可以得到大区间的最优解.

链接 : https://vjudge.net/problem/51Nod-1021 

代码 : 

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#define d(x) for(int i = 1 ;i<=(x) ; i++)
#define Init_(x)	memset(x,0,sizeof(x))
#define pi acos(-1)
#define while_(x) while(scanf("%d",&x)!=EOF)
#define while_c(x) while(x--)
#define scanf_(x,y)  scanf("%d%d",&x,&y)
#define mst(a,b) memset((a),(b),sizeof(a))

using namespace std ;
const int inf = 1<<30  ;
typedef long long LL ;
const LL Mod = 1e9+7 ;

int dp[1005][1005] ;
int sum[1005] ;

int main()
{
	// 区间 dp
	int n ;
	cin >> n ;
	sum[0] = 0 ;
	memset(dp,0x3f3f,sizeof(dp));
	for(int i = 1  ;i <=n ; i++ )
	{
		int x ;
		cin >>x ;
		sum[i] = sum[i-1] + x ;
		dp[i][i] = 0  ;
	}


	for(int len =  2 ;len<=n ; len++) // 区间长度
	{
		for(int i = 1 ; i<=n ; i++)// 枚举起点
		{
			int j = len-1+i ; // 区间终点
			if(j>n)
			continue; // yuejie
			for(int k = 1 ; k<j ; k++) // 分割点
			{
				dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]) ;
			
			}
		}
	}
	
//	
//	for(int i = 1 ; i<=n ; i++)
//	{
//		for(int j = 1 ; j<=n; j++)
//		{
//			cout<<dp[i][j]<<" ";
//		}
//		cout<<endl;
//	}
cout<<dp[1][n] <<endl;

	return 0 ;
}

今天先写到这里,等学了其他的再加补充.

 

 

 

 

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值