区间DP
区间DP
区间DP也是线性DP,它把区间当成DP的阶段,用区间的两个端点描述状态和处理状态的转移。区间DP的主要思想就是先在小区间进行DP得到最优解,然后再合并小区间的最优解求得大区间的最优解。
例题
石子合并
有 n 堆石子排成一排,第 i 堆石子有 \(a_i\) 颗,每次我们可以选择相邻的两堆石子合并,代价是两堆石子数目的和,现在我们要一直合并这些石子,使得最后只剩下一堆石子,问总代价最少是多少?
将所有区间 [l, r] 的最小代价均算出来,而为了计算合并区间 [l, r] 的最小代价,我们需要先计算合并所有满足 $i \le k < j $ 的区间 $[i, k] + [k + 1, j] $ 的最小代价(最优子结构)
而且我们仅需要关注合并区间的最小代价,而不需要关注如何合并区间(无后效性)
故可以利用动态规划求解,时间复杂度 $O(n^3) $
- 普通石子合并 - 洛谷 P1775 石子合并(弱化版)
//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long
using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*
*/
const int maxm = 3e2 + 5, inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f, mod = 998244353;
ll n, a[maxm], dp[maxm][maxm];
void solve(){
cin >> n;
for(int i = 1; i <= n; ++ i){
cin >> a[i]; a[i] += a[i - 1];
}
for(int len = 2; len <= n; ++ len){// 枚举合并区间长度
for(int i = 1; i + len - 1 <= n; ++ i){// 枚举区间开始位置
int j = i + len - 1;
dp[i][j] = INF;
for(int k = i; k < j; ++ k){// 枚举中间划分点
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + a[j] - a[i - 1]);
}
}
}
cout << dp[1][n] << '\n';
return ;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int _ = 1;
// cin >> _;
while(_ --){
solve();
}
return 0;
}
- 环形石子合并 - 洛谷 P1880 [NOI1995] 石子合并
圆形操场:第一堆和最后一堆可以合并!!!
处理环形问题,在原本的石子堆之后再加 n 堆一样的石子,再对这 2n 堆石子跑DP,最后遍历每 n 堆石子构成的区间,取结果的最大和最小即为最终答案
Qiansui_code
- 洛谷 P5569 [SDOI2008] 石子合并
难,详见洛谷评论区,待补充
括号序列
题意
给定一个长度为 n 的字符串 s,字符串由 (, ), [, ] 组成,问其中最长的合法子序列有多长?也就是说,我们要找到最大的 m,使得存在 \(i_1,i_2,…,i_m\) 满足 \(1≤i_1<i_2<⋯<i_m≤n\) 并且 \(s_{i_1}s_{i_2}…s{i_m}\) 是一个合法的括号序列。
定义合法序列:
空串是一个合法的括号序列
若 A 是一个合法的括号序列,则 (A), [A] 也是合法的括号序列
若 A, B 都是合法的括号序列,则 AB 也是合法的括号序列
思路
区间 DP
状态:
dp[i][j] 代表区间 [i, j] 合法的最大序列长度
转移:
若 A 串[i, j] 符合题解,且 ss[i] == '(' && ss[j] == ')' || ss[i] == '[' && ss[j] == ']' ,dp[i - 1][j + 1] = dp[i][j] + 2
区间 [i, j] 的最大合法序列程度即为 $\max(dp[i][k] + dp[k + 1][j]), i \le k < j $
代码
int n, dp[maxm][maxm];
string ss;
void solve(){
cin >> n >> ss;
ss = "#" + ss;
for(int len = 2; len <= n; ++ len){
for(int i = 1; i + len - 1 <= n; ++ i){
int j = i + len - 1;
if(ss[i] == '(' && ss[j] == ')' || ss[i] == '[' && ss[j] == ']')
dp[i][j] = dp[i + 1][j - 1] + 2;
for(int k = i; k < j; ++ k){
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j]);
}
}
}
cout << dp[1][n] << '\n';
return ;
}
String painter
https://vjudge.net/problem/HDU-2476
本例题详可见文末链接 1
\(DP[i][j]\)表示区间\([i,j]\)内从空白串转换到B的最少操作次数
//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long
using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*
区间DP例题
*/
const int maxm=1e2+5,inf=0x3f3f3f3f,mod=998244353;
string ss,tt;
int dp[maxm][maxm];
void solve(){
while(cin>>ss>>tt){
int n=ss.size();
//计算将空白串转为tt串
for(int i=1;i<=n;++i) dp[i][i]=1;
for(int len=2;len<=n;++len){
for(int i=1;i+len-1<=n;++i){
int j=i+len-1;
dp[i][j]=inf;
if(tt[i-1]==tt[j-1])
dp[i][j]=dp[i+1][j];
else
for(int k=i;k<j;++k){
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
}
}
}
//计算将ss串转为tt串
for(int i=1;i<=n;++i){
if(ss[i-1]==tt[i-1]){
dp[1][i]=dp[1][i-1];
}else{
for(int j=1;j<i;++j){
dp[1][i]=min(dp[1][i],dp[1][j]+dp[j+1][i]);
}
}
}
cout<<dp[1][n]<<'\n';
}
return ;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int _=1;
// cin>>_;
while(_--){
solve();
}
return 0;
}
综合应用
- 转移条件限制 洛谷 P3146 [USACO16OPEN] 248 G
状态:$DP[i][j] $ 表示区间[i, j] 可以合并出的最大值
转移:朴素的 $O(n^3) $ 遍历时,第三层循环转移的条件是 $dp[i][k] = dp[k + 1][j] $ 且 \(dp[i][k] \ne 0\)
因为只有当区间左右能合并成一个数且左右相等时才能再次合并
for(int len = 2; len <= n; ++ len){
for(int i = 1; i + len - 1 <= n; ++ i){
int j = i + len - 1;
for(int k = i; k < j; ++ k){
if(dp[i][k] == dp[k + 1][j] && dp[i][k]){// 转移条件~
dp[i][j] = max(dp[i][j], dp[i][k] + 1);
}
}
ans = max(ans, dp[i][j]);
}
}
代码:Qiansui_code
- 环形区间DP 洛谷 P1063 [NOIP2006 提高组] 能量项链
依旧是基础的 $O(n^3) $ 思想的区间 DP,但是本题的下标应当扩大一位,为了便于状态的转移,详见代码
for(int len = 3; len <= n + 1; ++ len){// 头,间,尾至少三个位置
for(int i = 1; i + len - 1 <= n * 2; ++ i){// 2n 处理环成链
int j = i + len - 1;
for(int k = i + 1; k < j; ++ k){// k 需要在 (i, j) 中,不能重合边界
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + a[i] * a[j] * a[k]);
}
}
}
代码:Qiansui_code
相关资料
1.罗勇军老师整理
https://blog.csdn.net/weixin_43914593/article/details/10616385
2.dx123 428【模板】区间DP 石子合并
本文来自博客园,作者:Qiansui,转载请注明原文链接:https://www.cnblogs.com/Qiansui/p/17546052.html