DP---动态规划——【区间DP】【更新中】

参考博客:https://blog.csdn.net/qq_33583069/article/details/52216737#commentsedit

                 https://blog.csdn.net/hjf1201/article/details/78799325

什么是区间DP

所谓区间dp,就是在一个区间上进行的dp, 一般通过将大区间分割成小区间进行dp。 
区间型动态规划,又称为合并类动态规划,是线性动态规划的扩展,它在分阶段地划分问题时,与阶段中元素出现的顺序和由前一阶段的区间中哪些元素合并而来有很大的关系。

区间dp的特征:给的数据为一条链或者一个环(环形dp)

区间动归状态转移方程及一般动规过程:

for k:=1 to n-1 do    //区间长度
  for i:=1 to n-k do     //区间起点
    for j:=i to i+k-1 do     //区间中任意点
      dp[i,i+k]:=max{dp[i,j] + dp[j+1,i+k] + a[i,j] + a[j+1,i+k]};


1. 状态转移方程字面意义:寻找区间dp[i,i+k]的一种合并方式dp[i,j] + dp[j+1,i+k],使得其值最大或最小。
2. 区间长度k必须要放到第一层循环,来保证方程中状态dp[i,j]、dp[j+1,i+k]值在dp[i,i+k]之前就已计算出来。
3. 其中a[i,j]+a[j+1,i+k]可以不要,也可以灵活多变,指的是合并区间时产生的附加值。

区间DP伪代码:

//mst(dp,0) 初始化DP数组
for(int i=1;i<=n;i++)
{
    dp[i][i]=初始值
}
for(int len=2;len<=n;len++)  //区间长度
for(int i=1;i<=n;i++)        //枚举起点
{
    int j=i+len-1;           //区间终点
    if(j>n) break;           //越界结束
    for(int k=i;k

 

石子和并(一排)题目连接

石子合并(一)

时间限制:1000 ms  |  内存限制:65535 KB

难度:3

描述 

有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。

输入

有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开

输出

输出总代价的最小值,占单独的一行

样例输入

 

3

1 2 3

7

13 7 8 16 21 4 18

样例输出

 

9

239

思路:状态转移方程 dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]) (dp【i】【j】表示合并第i堆到第j堆的最小代价

       这个题石子是 一排进行排列的,取中间的一个元素k进行区间分割,从不同的起点枚举不同区间的长度,进行状态判断,w[i][j]区间的值可用sum[j]-sum[i-1]进行计算。

    

#include 
#include 
#include 
#include 
using namespace std;
const int maxn=205;
int sum[maxn];
int dpmin[maxn][maxn];
int main()
{
    int n;
    scanf("%d",&n);
    memset(dpmin,0x3f,sizeof(dpmin));
    for(int i=1;i<=n;i++)
    {
        int temp;
        scanf("%d",&temp);
        sum[i]=sum[i-1]+temp;
        dpmin[i][i]=0;
    }
    for(int len=2;len<=n;len++)///区间长度
    for(int i=1;i<=n;i++)///枚举起点(此处也可以反着枚举,除了dp[i][len]在二重循环里面赋初值
    {                   ///较大外其他无区别,这里的方法就行无需考虑)
       int j=i+len-1;
       if(j>n) continue;
       for(int k=i;k

P1880 [NOI1995]石子合并【圆形排列】

题解来自:https://www.luogu.org/problemnew/solution/P1880

题解一:

这是一道区间dp十分经典的模板题,让我们揣测一下,前辈们是如何得到这个状态转移方程的。

首先,要计算合并的最大值、最小值,既然是动态规划,我们需要洞悉其中一些关联且确定的状态。

以下以最大值为例。

既然是最大值,那么求得的结果是否满足每一区间都是该区间所能达得到的的最大值?

显然是这样的。反证法:倘若有一个区间不是,那么换做该区间取得最大值的方案,最终结果将比原得分大。显然必定满足任意区间得分一定是该区间内的最大值。

这样我们可以定义状态f[i][j],表示i到j合并后的最大得分。其中1<=i<=j<=N。

既然这样,我们就需要将这一圈石子分割。很显然,我们需要枚举一个k,来作为这一圈石子的分割线。

这样我们就能得到状态转移方程:

f[i][j] = max(f[i][k] + f[k+1][j] + d(i,j));其中,1<=i<=<=k

d(i,j)表示从i到j石子个数的和。

那么如何编写更快的递推来解决这个问题?

在考虑如何递推时,通常考虑如下几个方面:

是否能覆盖全部状态?

求解后面状态时是否保证前面状态已经确定?

是否修改了已经确定的状态?

也就是说,在考虑递推顺序时,务必参考动态规划的适应对象多具有的性质,具体参考《算法导论》相关或百度百科或wiki。

既然之前说过我们需要枚举k来划分i和j,那么如果通过枚举i和j进行状态转移,很显然某些k值时并不能保证已经确定过所需状态。

如,i=1 to 10,j=1 to 10,k=1 to 9.当i=1,j=5,k=3时,显然状态f[k+1][j]没有结果。

那么,我们是不是应该考虑枚举k?

但这样i和j就难以确定了。

我们不难得到一个两全的方法:枚举j-i,并在j-i中枚举k。这样,就能保证地推的正确。

代码:

#include  
#include  
#include  
using namespace std;   
int n,minl,maxl,f1[300][300],f2[300][300],num[300];  
int s[300];  
inline int d(int i,int j){return s[j]-s[i-1];}  
//转移方程:f[i][j] = max(f[i][k]+f[k+1][j]+d[i][j];
int main()  
{   
    scanf("%d",&n);  
    for(int i=1;i<=n+n;i++)  
    {  
        scanf("%d",&num[i]);  
        num[i+n]=num[i];  
        s[i]=s[i-1]+num[i];  
    }  
    for(int p=1;p

题解二:

如果上面的代码看不懂look here:接着上面的题用断环为链的方法,将链复制一倍,控制第二层循环枚举时乘二减去长度,最后

算答案时在从起点枚举到n即可

上代码:

#include 
#include 
#include 
#include 
using namespace std;
const int maxn=205;
int sum[maxn],w[maxn];///w[]是当i为n~2n时sum[i]的值
int dpmin[maxn][maxn],dpmax[maxn][maxn];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&w[i]);
        w[i+n]=w[i];
    }
    for(int i=1;i<=2*n;i++)
    {
        sum[i]=sum[i-1]+w[i];
    }
    for(int len=2;len<=n;len++)///区间长度
    for(int i=1;i<=n*2-len;i++)///这里要注意
    {               
       int j=i+len-1;
       dpmin[i][j]=1e9;
       for(int k=i;k

 

 P1063 能量项链

题目描述

在 MarsMars 星球上,每个 MarsMars 人都随身佩带着一串能量项链。在项链上有 NN颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。因为只有这样,通过吸盘(吸盘是 MarsMars 人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。如果前一颗能量珠的头标记为 mm ,尾标记为 rr ,后一颗能量珠的头标记为r,尾标记为 nn ,则聚合后释放的能量为 m \times r \times nm×r×n ( MarsMars 单位),新产生的珠子的头标记为 mm ,尾标记为 nn 。

需要时, MarsMars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。

例如:设 N=4N=4 , 44 颗珠子的头标记与尾标记依次为 (2,3) (3,5) (5,10) (10,2)(2,3)(3,5)(5,10)(10,2) 。我们用记号⊕表示两颗珠子的聚合操作,( jj ⊕ kk)表示第 j,kj,k 两颗珠子聚合后所释放的能量。则第 44 、 11 两颗珠子聚合后释放的能量为:

( 44 ⊕ 11 ) =10 \times 2 \times 3=60=10×2×3=60 。

这一串项链可以得到最优值的一个聚合顺序所释放的总能量为:

(( 44 ⊕ 11 )⊕ 22 )⊕ 33 )= 10 \times 2 \times 3+10 \times 3 \times 5+10 \times 5 \times 10=71010×2×3+10×3×5+10×5×10=710。

输入输出格式

输入格式:

 

第一行是一个正整数 N(4≤N≤100)N(4≤N≤100) ,表示项链上珠子的个数。第二行是 NN 个用空格隔开的正整数,所有的数均不超过 10001000 。第 ii 个数为第 ii 颗珠子的头标记 (1≤i≤N)(1≤i≤N) ,当 ii 时,第 ii 颗珠子的尾标记应该等于第 i+1i+1 颗珠子的头标记。第 NN 颗珠子的尾标记应该等于第 11 颗珠子的头标记。

至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。

 

输出格式:

 

一个正整数 E(E≤2.1 \times (10)^9)E(E≤2.1×(10)9) ,为一个最优聚合顺序所释放的总能量。

 

输入输出样例

输入样例#1: 复制

4
2 3 5 10

输出样例#1: 复制

710

大佬题解:来自:https://www.luogu.org/problemnew/solution/P1063

//区间动规 //重点就是将整体划分为区间,小区间之间合并获得大区间 //状态转移方程的推导如下 //

一、将珠子划分为两个珠子一个区间时,这个区间的能量=左边珠子*右边珠子*右边珠子的下一个珠子 //

二、区间包含3个珠子,可以是左边单个珠子的区间+右边两珠子的区间,或者左边两珠子的区间右边+单个珠子的区间 //即,先合并两个珠子的区间,释放能量,加上单个珠子区间的能量(单个珠子没有能量。。) //Energy=max(两个珠子的区间的能量+单个珠子区间的能量,单个珠子的区间的能量+两个珠子的区间的能量 ) //

三、继续推4个珠子的区间,5个珠子的区间。 //于是可以得到方程:Energy=max(不操作的能量,左区间合并后的能量+右区间合并后的能量+两区间合并产生能量) //两区间合并后产生的能量=左区间第一个珠子*右区间第一个珠子*总区间后面的一个珠子

代码一:

#include
using namespace std;
int n,e[300],s[300][300],maxn=-1;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){cin>>e[i];e[i+n]=e[i];}
    //珠子由环拆分为链,重复存储一遍
    for(int i=2;i<2*n;i++){
        for(int j=i-1;i-j=1;j--){//从i开始向前推
            for(int k=j;kmaxn)maxn=s[j][i];//求最大值 
        }
    } 
    cout<

 

代码二:与上面的思路相同一些细节不同

#include
#include
#include
#include
#include
using namespace std;
int n;
int a[220];
int dp[220][220];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		a[i+n] = a[i];
	}
	for(int r=2;r<=2*n;r++)
	{
		for(int l=r-1;l>=1&&r-l+1<=n;l--)
		{
			for(int k=l;k

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(dp)