[算法]区间dp
关键词:区间DP枚举顺序,环形处理
区间dp一般操作先枚举长度,第二层枚举左端点,第三层枚举中间断点
一般格式:设f[l][r]为[l,r]之间的答案
for(int i=1;i<=n;i++){//枚举r-l大小
for(int l=1;l+i<=n;l++){//枚举左端点,保证存在右端点
int r=l+i;//算出右端点
for(int k=l;k<r;k++){//枚举中间断点
f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+cost);//转移方程
}
}
}
例题1:简化题目即合并左右两个数直到剩余一个数,合并的过程中代价需要加上两个数之和,求最小代价
合并[l,r]之间的数,代价一定是[l,r]之间之和,所以可以预处理前缀和(即s数组)即可
#include <bits/stdc++.h>
using namespace std;
int n,a[305],f[305][305],s[305];
int main(){
cin>>n;
memset(f,0x3f3f3f,sizeof(f));
for(int i=1;i<=n;i++){
cin>>a[i];
f[i][i]=0;
s[i]=s[i-1]+a[i];
}
for(int i=1;i<=n;i++){//此处表示[l+1,r]之间长度右端点变为l+i
for(int l=1;l+i<=n;l++){
for(int k=l;k<=l+i;k++){
f[l][l+i]=min(f[l][l+i],f[l][k]+f[k+1][l+i]+s[l+i]-s[l-1]);
}
}
}
cout<<f[1][n];
return 0;
}
例题2:简化题目即对于一个字符串最少添加多少个字符才能变为回文字符串
针对[l,r]之间的回文(前提求出[l+1,r-1]的值也就是[l+1,r-1]此时已经为回文),当s[l]=s[r]说明[l+1,r-1]可以向外扩f[l][r]=f[l+1][r-1];当s[l]!=s[r]说明需要在l上插入s[r]字符或者在r上插入s[l]字符,因为求最小值所以求min{f[l+1][r],f[l][r-1]}+1就是此时f[l][r]的答案
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
string s;
int f[N][N];
int main(){
cin>>s;
memset(f,0x3f3f3f,sizeof(f));
for(int i=0;i<(int)s.size();i++){
f[i][i]=0;
if(i<(int)s.size()-1){
if(s[i+1]==s[i])f[i][i+1]=0;
else f[i][i+1]=1;
}
}
for(int i=2;i<(int)s.size();i++){
for(int l=0;l+i<(int)s.size();l++){
int r=l+i;
if(s[l]==s[r])f[l][r]=f[l+1][r-1];
else f[l][r]=min(f[l+1][r],f[l][r-1])+1;
}
}
cout<<f[0][s.size()-1]<<"\n";
return 0;
}
例题3:化简题目即n个数成环,相邻两个数可以合并,代价为两数之和,询问代价最大与代价最小分别为多少
本题是例题1进阶版,例题1不是环并且只询问最小值,本题虽然是环,但可以拆环成链,而且寻找最值的区间长度为n,只需要复制一遍即可,剩下思路与例题1相似
#include <bits/stdc++.h>
using namespace std;
const int N = 205;
int n,a[N],f1[N][N],f2[N][N],s[N];
int main(){
cin>>n;
memset(f1,0x3f3f3f,sizeof(f1));
memset(f2,0,sizeof(f2));
for(int i=1;i<=n;i++){
cin>>a[i];
a[i+n]=a[i];
f1[i][i]=0;
f1[i+n][i+n]=0;
f2[i][i]=0;
f2[i+n][i+n]=0;
if(i>1){
f1[i-1][i]=a[i-1]+a[i];
f1[i+n-1][i+n]=a[i+n-1]+a[i+n];
f2[i-1][i]=a[i-1]+a[i];
f2[i+n-1][i+n]=a[i+n-1]+a[i+n];
}
}
f1[n][n+1]=a[n]+a[n+1];
f2[n][n+1]=a[n]+a[n+1];
for(int i=1;i<=n*2;i++)s[i]=s[i-1]+a[i];
for(int i=2;i<=n;i++){
for(int l=1;l+i<=n*2;l++){
int r=l+i;
for(int k=l;k<r;k++){
f1[l][r]=min(f1[l][r],f1[l][k]+f1[k+1][r]+(s[r]-s[l-1]));
f2[l][r]=max(f2[l][r],f2[l][k]+f2[k+1][r]+(s[r]-s[l-1]));
}
}
}
int ans1=INT_MAX,ans2=0;
for(int l=1;l<=n;l++){
ans1=min(ans1,f1[l][l+n-1]);
ans2=max(ans2,f2[l][l+n-1]);
}
cout<<ans1<<"\n"<<ans2<<"\n";
return 0;
}
例题4:简化题目即一个环上每次可以合并相邻的两个数,价值是这两个数相乘再乘上下个位置的数
第一步与例题3一样,都是拆环成链,后面与例题1相似,不同之处本题后面需要加a[l]a[k+1]a[r+1]而不是s[r]-s[l-1]
#include <bits/stdc++.h>
using namespace std;
int n;
long long ans=0,a[205],f[205][205];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
a[i+n]=a[i];
}
for(int i=1;i<=n;i++){
for(int l=1;l+i<n*2;l++){
int r=l+i;
for(int k=l;k<r;k++){
f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+a[l]*a[k+1]*a[r+1]);
}
}
}
for(int i=1;i<=n;i++){
ans=max(ans,f[i][i+n-1]);
}
cout<<ans<<"\n";
return 0;
}
例题5:简化题目,每次可以选择相邻且相等的数合并,合并结果是合并前的数+1,询问合并后的最大整数是多少
可以设立f[l][r]定义为[l,r]可以合并到剩下一个数,这个数也就是f[l][r],反之则令f[l][r]=0也就是无法合并至剩下一个数
转移方程也就是f[l][r]=max(f[l][r],f[l][k]+1)当f[l][k]=f[k+1][r]且f[l][k]不为0时转移
#include <bits/stdc++.h>
using namespace std;
int n,f[250][250],ans=0;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>f[i][i];
ans=max(ans,f[i][i]);
}
for(int i=1;i<=n;i++){
for(int l=1;l+i<=n;l++){
int r=l+i;
for(int k=l;k<r;k++){
if(f[l][k] == f[k+1][r] && f[l][k]){
f[l][r]=max(f[l][r],f[l][k]+1);
ans=max(ans,f[l][r]);
}
}
}
}
cout<<ans<<"\n";
return 0;
}
例题6:简化题目,一次涂色只能选择连续区间与相同颜色进行涂色,且后续涂色可以覆盖之前的颜色,询问最少涂色次数
针对[l,r]
1.当s[l]=s[r]的时候,s[r]颜色可以在涂s[l]颜色的时候一起涂,涂色次数也就是f[l][r-1]
2.当s[l]!=s[r]的时候,枚举中间断点k,求f[l][k]+f[k+1][r]的最小值当作此时的涂色次数
#include <bits/stdc++.h>
using namespace std;
string s;
int f[60][60];
int main(){
cin>>s;
int n=s.size();
memset(f,0x3f3f3f,sizeof(f));
for(int i=1;i<=n;i++)f[i][i]=1;
for(int i=1;i<=n;i++){
for(int l=1;l+i<=n;l++){
int r=l+i;
if(s[l-1]==s[r-1]){
f[l][r]=f[l][r-1];
}else{
f[l][r]=f[l][l]+f[l+1][r];
for(int k=l+1;k<r;k++){
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]);
}
}
}
}
cout<<f[1][n]<<"\n";
return 0;
}
例题7:简化题目,有多少种初始数组可以按照指定规则得到理想数组
[思路借鉴]本题若按照之前例题思考模板思考会发现无法下手,本题每一个位置都与前一位数大小有关,本题有两种放置方法,一个放前面,一个放后面,设立f[i][j][opt],若opt=0代表[i,j]最后放置的第i人,若opt=1代表[i,j]最后放置的第j人
现在有四种情况
1.前一步放到前面,当前放到前面(条件:a[i]<a[i+1])
f[i][j][0]+=f[i+1][j][0]
2.前一步放到前面,当前放到后面(条件:a[j]>a[i])
f[i][j][1]+=f[i][j-1][0]
3.前一步放到后面,当前放到前面(条件:a[i]<a[j])
f[i][j][0]+=f[i+1][j][1]
4.前一步放到后面,当前放到后面(条件:a[j]>a[j-1])
f[i][j][1]+=f[i][j-1][1]
#include <bits/stdc++.h>
using namespace std;
const long long mod=19650827;
int n,a[1005];
long long f[1005][1005][2];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
f[i][i][0]=1;
}
for(int i=n;i>=1;i--){
for(int j=i+1;j<=n;j++){
if(a[i+1]>a[i]){
f[i][j][0]+=f[i+1][j][0];
}
if(a[j]>a[j-1]){
f[i][j][1]+=f[i][j-1][1];
}
if(a[i]<a[j]){
f[i][j][0]+=f[i+1][j][1];
f[i][j][1]+=f[i][j-1][0];
}
f[i][j][0]%=mod;
f[i][j][1]%=mod;
}
}
cout<<(f[1][n][0]+f[1][n][1])%mod<<"\n";
return 0;
}

浙公网安备 33010602011771号