本篇文章总结动态规划的第二大类问题,即线性dp问题和区间dp问题。
题目1:数字三角形
题目描述:
这道题目怎么想?我们还是从y总的dp分析法出发。
在定义坐标的行和列时,我们如下的定义方式:
这样的话就好理解了:
#include<bits/stdc++.h>
using namespace std;
const int INF=1e9;
int a[505][505];
int f[505][505];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
for(int i=0;i<=n;i++)
for(int j=0;j<=i+1;j++)//这个范围要往后开一些,否则的话很可能就会导致计算错误
f[i][j]=-INF;
f[1][1]=a[1][1];
for(int i=2;i<=n;i++)
for(int j=1;j<=i;j++)
{
f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);//状态转移方程求最大值
}
int res=-INF;
for(int i=1;i<=n;i++)
res=max(res,f[n][i]);
cout<<res;
return 0;
}
题目2:最长上升子序列
最长上升子序列问题是线性dp里面也是整个动态规划里面的经典问题,最长上升子序列的描述如下:
根据问题的描述不难发现是从n个数里面选出最长的子序列。选法其实有很多种,可以就只有一个数字组成,也可以是2个,3个数等等组成的序列。
闫总的dp分析法。
代码如下:
#include<bits/stdc++.h>
using namespace std;
int n;
int a[1010];
int f[1010];
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];//输入系列数字
for(int i=1;i<=n;i++)
{
f[i]=1;//因为自己肯定算一个所以初始化f[i]一定是1
for(int j=1;j<i;j++)//从当前数的前面去比较
if(a[j]<a[i])//必须满足的条件
f[i]=max(f[i],f[j]+1);//更新f[i]的值
}
int res=0;
for(int i=1;i<=n;i++) res=max(res,f[i]);//找出再大的数
cout<<res;
return 0;
}
如果说,让输出最长子序列是什么,那中间再加上一个数组就可以了。
#include<bits/stdc++.h>
using namespace std;
int n;
int a[1005];
int f[1005];
int g[1005];
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
f[i]=1;
g[i]=0;
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
if(f[i]<f[j]+1)
{
f[i]=f[j]+1;
g[i]=j;
}
}
}
int k=1;
for(int i=1;i<=n;i++)
if(f[k]<f[i])
k=i;
cout<<f[k]<<endl;
for(int i=0,len=f[k];i<len;i++)
{
cout<<a[k]<<" ";
k=g[k];
}
return 0;
}
题目3:最长公共子序列
这类题目也是很常见的一类问题,题目的描述为:
这道题目如果简单的分析的话很容易出错,我们先看如何用dp分析法进行分析。
代码如下:
#include<bits/stdc++.h>
using namespace std;
int f[1005][1005];
char a[1005],b[1005];
int n,m;
int main()
{
cin>>n>>m;
cin>>a+1>>b+1;//两个字符串要用字符型数组去存,如果用string的话下标是从0开始的,下面的代码会越界。
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
f[i][j]=max(f[i-1][j],f[i][j-1]);//中间的两个状态的最大值
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);//满足条件就更新最后一种情况
}
cout<<f[n][m];
return 0;
}
题目4:最短编辑距离
先看题目描述
这道题目也是动态规划里面的典型的题目,我们用y式dp分析法来分析这道题目。
三种情况。第一种情况可以理解为,前i-1个和j个已经匹配,要删掉第i个才能整体匹配。第二种情况表示前i个和前j-1个已经匹配,要想整体匹配,必须加上一个。第三种情况是分为两种,当a[i]==b[j]的时候表示不需要操作,加上0即可,当a[i]!=b[j]的时候,再加上一步修改操作,所以再加上1。
代码如下:
#include<bits/stdc++.h>
using namespace std;
int n,m;
char a[1005],b[1005];
int f[1005][1005];
int main()
{
cin>>n; cin>>a+1;
cin>>m; cin>>b+1;
for(int i=0;i<=n;i++) f[i][0]=i;
for(int i=0;i<=m;i++) f[0][i]=i;
//这两行是比较难考虑到的,第一行表示前i个和0个匹配,就要删除a的i个
//第二行表示a变成b要加上i个字符
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);//前两种情况
if(a[i]==b[j]) f[i][j]=min(f[i-1][j-1],f[i][j]);//第三种情况
else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
}
cout<<f[n][m];
return 0;
}
和这道题做法一样的一道题目
代码:
#include<bits/stdc++.h>
using namespace std;
char a[1005][1005];
int f[1005][1005];
int n,m;
int t;
int caozuo(char a[],char b[])//执行操作的函数,和上一题一样的代码
{
int la=strlen(a+1);int lb=strlen(b+1);
for(int i=0;i<=lb;i++) f[0][i]=i;
for(int i=0;i<=la;i++) f[i][0]=i;
for(int i=1;i<=la;i++)
for(int j=1;j<=lb;j++)
{
f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
if(a[i]==b[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);
else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
}
return f[la][lb];
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i]+1;//这里是第一次见到,学习字符串的存储
while(m--)
{
char b[15];
cin>>b+1>>t;
int ans=0;
for(int i=1;i<=n;i++)//遍历每一个
{
if(caozuo(a[i],b)<=t)
ans++;
}
cout<<ans<<endl;
}
return 0;
}
区间dp
题目1:石子合并
先看题目描述:
这道题目我暂时没有听懂,先放在这,先贴入y总的分析和代码:
代码:
#include <bits/stdc++.h>
using namespace std;
int n, dp[310][310], a[310], s[310];
int main() {
scanf("%d", &n);
for (int i = 1;i <= n; i++) scanf("%d", &a[i]), s[i] = s[i - 1] + a[i];
memset(dp, 0, sizeof dp);
for (int len = 2; len <= n; len++)
for (int i = 1;i + len - 1 <= n; i++) {
int j = i + len - 1; dp[i][j] = 0x3f3f3f3f;
for (int k = i; k <= j; k++) dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + s[j] - s[i - 1]);
}
printf("%d\n", dp[1][n]);
return 0;
}