Living-Dream 系列笔记 第48期

区间 dp:以区间为子问题的 dp。

特征:

  • 从左往右 / 从右往左递推会得到不同结果;

  • 常为 合并类 / 拆分类 / 处理两端类;

  • 要么枚举中间断点,要么枚举两端点。

T1

\(dp_{i,j}\) 表示合并区间 \([i,j]\) 所需代价。易得答案为 \(dp_{1,n}\)

初始状态即为 \(dp_{i,i}=0\),其余为 \(\infty\)

考虑枚举中转点 \(k\),将区间 \([i,j]\) 分为区间 \([i,k]\)\([k+1,j]\) 分别进行合并。

因此有转移:

\[dp_{i,j}=dp_{i,k}+dp_{k+1,j}+sum_j-sum_{i-1} \]

\(sum_i\)\(m_i\) 的前缀和)

\(O(n^3)\) 转移即可。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e2+5;
int n,a[N],sum[N];
int dp[N][N];
signed main(){
ios::sync_with_stdio(0);
cin>>n;
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++)
cin>>a[i],sum[i]=sum[i-1]+a[i],dp[i][i]=0;
for(int i=2;i<=n;i++){
for(int j=1;j+i-1<=n;j++){
int s=j,e=j+i-1;
for(int k=s;k<=e-1;k++)
dp[s][e]=min(dp[s][e],dp[s][k]+dp[k+1][e]+sum[e]-sum[s-1]);
}
}
cout<<dp[1][n];
return 0;
}

T2

\(dp_{i,j}\) 表示合并区间 \([i,j]\) 所能获得的最大数。易得答案为 \(dp_{1,n}\)

初始状态即为 \(dp_{i,i}=a_i\),其余为 \(-\infty\)

显然有转移:

\[dp_{i,j}=dp_{i,k}+1(dp_{i,k}=dp_{k+1,j}) \]

\(O(n^3)\) 转移即可。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e2+5;
int n,a[N],sum[N];
int dp[N][N];
signed main(){
ios::sync_with_stdio(0);
cin>>n;
memset(dp,0xcf,sizeof(dp));
for(int i=1;i<=n;i++)
cin>>a[i],dp[i][i]=a[i];
for(int i=2;i<=n;i++){
for(int j=1;j+i-1<=n;j++){
int s=j,e=j+i-1;
for(int k=s;k<=e-1;k++)
if(dp[s][k]==dp[k+1][e])
dp[s][e]=max(dp[s][e],dp[s][k]+1);
}
}
int ans=-1e9;
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
ans=max(ans,dp[i][j]);
cout<<ans;
return 0;
}

T3

solution link

代码要去掉多测。

作业 T1

串串 + 区间 dp 简单题(?)。

\(dp_{i,j}\) 表示将区间 \([i,j]\) 涂成目标颜色所需的最少涂色次数。易得答案为 \(dp_{1,n}\)

初始状态即为 \(dp_{i,i}=1\),其余为 \(\infty\)

分类讨论:

  • \(s_i=s_j\),则相当于在涂 \(i\) / \(j\) 时顺带将 \(j\) / \(i\) 涂上了,无需额外再涂一次,于是有转移 \(dp_{i,j}=\max(dp_{i+1,j},dp_{i,j-1})\)

  • 否则,有转移 \(dp_{i,j}=\max(dp_{i,j},dp_{i,k}+dp_{k+1,j})\)

\(O(n^3)\) 转移即可。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e2+5;
string s;
int n;
int dp[N][N];
signed main(){
ios::sync_with_stdio(0);
cin>>s,n=s.size(),s='#'+s;
memset(dp,0x3f,sizeof(dp));
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;
if(s[i]==s[j])
dp[i][j]=min(dp[i+1][j],dp[i][j-1]);
else
for(int k=i;k<j;k++)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
}
}
cout<<dp[1][n];
return 0;
}

作业 T2

\(dp_{i,j}\) 表示区间(即一棵子树) \([i,j]\) 的最高加分。易得答案为 \(dp_{1,n}\)

考虑枚举根 \(k\),则其左子树为 \([i,k]\),右子树为 \([k+1,j]\)

于是有转移:

\[dp_{i,j}=dp_{i,k-1} \times dp_{k+1,j}+dp_{k,k} \]

注意要特判 \(dp_{i+1,i}\)\(dp_{i,i-1}\)(即只有左子树的节点 / 只有右子树的节点 / 叶子节点)。

至于输出前序遍历,我们每次更新答案时记录根,然后递归输出即可。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=31;
int n;
int dp[N][N];
int root[N][N];
void print(int l,int r){
if(l>r) return;
cout<<root[l][r]<<' ';
if(l==r) return;
print(l,root[l][r]-1);
print(root[l][r]+1,r);
}
signed main(){
ios::sync_with_stdio(0);
cin>>n;
memset(dp,0xcf,sizeof(dp));
for(int i=1;i<=n;i++)
cin>>dp[i][i],dp[i][i-1]=dp[i+1][i]=1,root[i][i]=i;
for(int i=2;i<=n;i++){
for(int j=1;j+i-1<=n;j++){
int s=j,e=j+i-1;
for(int k=s;k<=e;k++)
if(dp[s][e]<dp[s][k-1]*dp[k+1][e]+dp[k][k])
dp[s][e]=dp[s][k-1]*dp[k+1][e]+dp[k][k],
root[s][e]=k;
}
}
cout<<dp[1][n]<<'\n';
print(1,n);
return 0;
}
posted @   _XOFqwq  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示