区间DP基本类型

  • 基础区间DP(板子题目)
    这种DP问题很直接,明确的告诉你物品顺序不能改变,只需要套用区间DP的板子解题即可,状态转移方程可以总结为

    dp[l][r]=std::min(dp[l][r],dp[l][k]+dp[k+1][r]+k处产生的代价);( k->[l,r) )
    例如:石子合并,能量项链

  • 基础DP的升级版
    这个类型的DP难度往往体现在计算k处产生的价值上面,k的价值不再是和k+1项,k-1项有关,这样使得题目有一定的迷惑性,但是其实仍然可以套用前面基础DP的板子来进行求解,DP式子总结同上,重点阐述求解k的代价的过程
    例如:卡牌乘法(下面的阐述对象),矩阵连乘

    • 反向思考:既然模拟一个物品被抽出后合并原数组很困难,不妨假设他被抽走(不是指针),用dp[l][r]表示从l+1到r-1的代价,这个问题自然就迎刃而解了,k的代价就是(k项 与 l项 与 r项三者的乘积),值得注意的是状态转移的时候状态的开闭区间问题
memset (dp,0x7f,sizeof(dp));
for (int i=1;i<=n;i++) dp[i][i]=0;
for (int i=1;i+1<=n;i++) dp[i][i+1]=0;
for (int len=3;len<=n;len++) {
    for (int l=1;l+len-1<=n;l++) {
        int r=l+len-1;
        for (int k=l+1;k<r;k++) {
            dp[l][r]=std::min(dp[l][r],dp[l][k]+dp[k][r]+mapp[k]*mapp[l]*mapp[r]);
        }
    }
}
  • 状态转移与区间的前后区间有关
    状态转移不是仅仅是某1项或者2项有关,这种DP往往还需要配合许多预处理和额外处理的过程,状态定义还是左右端点,但是转移方程不但需要上述方程,还需要其他的转移(说不清。。。看例子)
    例子:字符串折叠

    • 对于每一个区间dp[l][r]都可以由dp[l][k]+dp[k+1][r]直接转移过来,但是对于每一个区间也存在着可以折叠的情况,我们需要从小到大枚举可能折叠(重复出现)的长度进行检验,对于确实可以折叠的情况继续进行转移,work函数求解折叠后的统计数字的位数,check检验是否可以折叠
for (int i=0;i<mapp.size();i++) dp[i][i]=1;
for (int len=2;len<=mapp.size();len++) {
    for (int l=0;l+len-1<mapp.size();l++) {
        int r=l+len-1;
        dp[l][r]=len;
        for (int k=l;k<r;k++) dp[l][r]=std::min(dp[l][r],dp[l][k]+dp[k+1][r]);
        for (int j=1;j<=len;j++) {
            if (len%j!=0) continue;
            else if (check(l,r,j)==1) dp[l][r]=std::min(dp[l][r],dp[l][l+j-1]+work(len/j)+2);
        }
    }
}
int check(int x,int y,int z)
{
    for (int i=x;i+z<=y;i++) {
        if (mapp[i]!=mapp[i+z]) return 0;
    }
    return 1;
}

int work(int x)
{
    int ls=1;
    while (x/=10) ls++;
    return ls;
}
  • 少数决策判断形
    这一类的dp通常转移方程不需要枚举断点,直接通过上几种显而易见的情况转移过来,通常情况下dp数组也不再是局限在2维,往往还有一个较小的第三维来更准确的描述状态
    例如:合唱队(接下来详细的说),关路灯

    • 首先说说为啥是区间DP,可以理解为每次进来一个人这个区间都会变大,而且已经确定的人顺序不会改变
    • 其次。。。为啥不需要枚举断点,因为显而易见的是dp[l][r]只能由dp[l+1][r]或者dp[l][r-1]转移过来,我们只需要解决处理这两种情况即可
    • 对于每个区间,又上一个区间的长度转移过来只有两种情况,这个人从左面进来或者右面进来,状态设计: dp[l][r][0]和dp[l][r][1],这两种状态同时转移,分别表示从左面进来和从右面进来。值得注意的是 :这里容易产生误解,似乎dp[l+1][r]这个状态想要转移过来只能从左面进入,其实这是一种概念混淆的表现,dp数组的下标之间的转移强调的是这位进来的人是谁而不是这个进来的人进来的方向,具体方向是由第三个维度表示的
    • 最后注意一开始的初始化,对于空队列来说没有左右之分,我们只需要默认其中一种状态即可(状态转移方程是累加的,左右情况都赋值的话会重复)(方便大家阅读就直接上全码了。。。)
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdio>

using namespace std;
const int MaxN=1000;
int a[MaxN+5],f[MaxN+5][MaxN+5][2];

int main()
{
	int n;
	cin>>n;
	for (int i=1;i<=n;i++) {
		cin>>a[i];
		f[i][i][0]=1;
	}
	
	for (int len=2;len<=n;len++) {
		for (int l=1;l+len-1<=n;l++) {
			int r=l+len-1;
			if (a[l]<a[l+1]) f[l][r][0]+=f[l+1][r][0];
			f[l][r][0]%=19650827;
			if (a[l]<a[r]) f[l][r][0]+=f[l+1][r][1];
			f[l][r][0]%=19650827;
			if (a[r-1]<a[r]) f[l][r][1]+=f[l][r-1][1];
			f[l][r][1]%=19650827;
			if (a[l]<a[r]) f[l][r][1]+=f[l][r-1][0];
			f[l][r][1]%=19650827;
		}
	}
	
	cout<<(f[1][n][0]+f[1][n][1])%19650827<<endl;
	return 0;
}

到此本文就结束了,给大家简单讲了一下dp中入门的入门。。。区间dp问题,希望对大家的OI有帮助,欢迎大家质疑,指正,提问
Ps:本文没有任何题目链接,但是题目均可以在网上或者洛谷上找到,如果大家想要详细题解也可以直接搜索,文章重点在区间DP类别,没有太注重解题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值