线性DP问题
前言
‘种一棵树的最好最好时间是十年前,其次是现在。’
前言内容:害,怎么说呢,我是一菜B,也没什么学习方法,就以在csdn上写文章做笔记吧,大家有兴趣的话可以参考,我写的比较乱,不太详细,如果有问题可以私信我,我尽量为大家解决问题
一、数字三角形
数字三角形是线性dp的算法之一,代表了一类问题,思想就是从后往前推,最后一排的数都是不变的,从倒数第二排开始,倒数第二排的数可以加上最后一排的数,可以从左加也可以从右开始加。
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输入格式
第一行包含整数 n
,表示数字三角形的层数。
接下来 n
行,每行包含若干整数,其中第 i
行表示数字三角形第 i
层包含的整数。
输出格式
输出一个整数,表示最大的路径数字和。
数据范围
1≤n≤500
,
−10000≤三角形中的整数≤10000
输入样例:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
输出样例:
30
代码如下图所示
#include<iostream>
using namespace std;
const int N = 510;
int f[N][N],w[N][N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
cin>>w[i][j];
}
for(int i=1;i<=n;i++) f[n][i]=w[n][i];
for(int i=n-1;i;i--)
{
for(int j=1;j<=i;j++)
{
f[i][j]=max(f[i+1][j]+w[i][j],f[i+1][j+1]+w[i][j]);
}
}
cout<<f[1][1]<<endl;
return 0;
}
二、最长子序列
给定一个长度为 N
的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N
。
第二行包含 N
个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤1000
,
−109≤数列中的数≤109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
代码如下(示例):
#include <iostream>
using namespace std;
const int N = 1010;
int n;
int w[N], f[N];
int main() {
cin >> n;
for (int i = 0; i < n; i++) cin >> w[i];
int mx = 1; // 找出所计算的f[i]之中的最大值,边算边找
for (int i = 0; i < n; i++) {
f[i] = 1; // 设f[i]默认为1,找不到前面数字小于自己的时候就为1
for (int j = 0; j < i; j++) {
if (w[i] > w[j]) f[i] = max(f[i], f[j] + 1); // 前一个小于自己的数结尾的最大上升子序列加上自己,即+1
}
mx = max(mx, f[i]);
}
cout << mx << endl;
return 0;
}
三.最长子序列Ⅱ
给定一个长度为 N
的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N
。
第二行包含 N
个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N≤100000
,
−109≤数列中的数≤109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
这题跟上一题一样,题目一样,就是对上一题的优化
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int a[N],f[N];
int cnt;
int find(int x)
{
int l=1,r=cnt;
while(l<r)
{
int mid=(l+r)/2;
if(f[mid]>=x) r=mid;
else l=mid+1;
}
return l;
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
f[++cnt]=a[1];
for(int i=2;i<=n;i++)
{
if(a[i]>f[cnt]) f[++cnt]=a[i];
else
{
int tmp=find(a[i]);
f[tmp]=a[i];
}
}
cout<<cnt<<endl;
return 0;
}
最长公共子序列
给定两个长度分别为 N
和 M
的字符串 A
和 B
,求既是 A
的子序列又是 B
的子序列的字符串长度最长是多少。
输入格式
第一行包含两个整数 N
和 M
。
第二行包含一个长度为 N
的字符串,表示字符串 A
。
第三行包含一个长度为 M
的字符串,表示字符串 B
。
字符串均由小写字母构成。
输出格式
输出一个整数,表示最大长度。
数据范围
1≤N,M≤1000
输入样例:
4 5
acbd
abedc
输出样例:
3
下面是y总的分析过程,基本上DP问题都是这样分析的
#include<iostream>//不会就看acwing题解榜首
using namespace std;
const int N = 1010;
char a[N],b[N];
int f[N][N];
int n,m;
int main()
{
cin>>n>>m>>a>>b;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(a[i]==b[j])
f[i][j]=f[i-1][j-1]+1;
else
f[i][j]=max(f[i-1][j],f[i][j-1]);
}
}
cout<<f[n][m]<<endl;
return 0;
}
最短编辑距离
给定两个字符串 A
和 B
,现在要将 A
经过若干操作变为 B
,可进行的操作有:
删除–将字符串 A
中的某个字符删除。
插入–在字符串 A
的某个位置插入某个字符。
替换–将字符串 A
中的某个字符替换为另一个字符。
现在请你求出,将 A
变为 B
至少需要进行多少次操作。
输入格式
第一行包含整数 n
,表示字符串 A
的长度。
第二行包含一个长度为 n
的字符串 A
。
第三行包含整数 m
,表示字符串 B
的长度。
第四行包含一个长度为 m
的字符串 B
。
字符串中均只包含大小写字母。
输出格式
输出一个整数,表示最少操作次数。
数据范围
1≤n,m≤1000
输入样例:
10
AGTCTGACGC
11
AGTAAGTAGGC
输出样例:
4
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1010;
char a[N],b[N];
int f[N][N];
int n,m;
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cin>>m;
for(int i=1;i<=m;i++) cin>>b[i];
for(int i=0;i<=n;i++) f[i][0]=i;
for(int i=0;i<=m;i++) f[0][i]=i;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
f[i][j]=min(f[i][j-1]+1,f[i-1][j]+1);
if(a[i]!=b[j]) f[i][j]=min(f[i][j],f[i-1][j-1]+1);
else f[i][j]=min(f[i][j],f[i-1][j-1]);
}
}
cout<<f[n][m]<<endl;
return 0;
}
编辑距离
给定 n
个长度不超过 10
的字符串以及 m
次询问,每次询问给出一个字符串和一个操作次数上限。
对于每次询问,请你求出给定的 n
个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。
每个对字符串进行的单个字符的插入、删除或替换算作一次操作。
输入格式
第一行包含两个整数 n
和 m
。
接下来 n
行,每行包含一个字符串,表示给定的字符串。
再接下来 m
行,每行包含一个字符串和一个整数,表示一次询问。
字符串中只包含小写字母,且长度均不超过 10
。
输出格式
输出共 m
行,每行输出一个整数作为结果,表示一次询问中满足条件的字符串个数。
数据范围
1≤n,m≤1000
,
输入样例:
3 2
abc
acd
bcd
ab 1
acbd 2
输出样例:
1
3
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string.h>
using namespace std;
const int N = 20,M=1e3+10;
int n,m;
char str[M][N];
int f[N][N];
int edit_distance(char a[],char b[])
{
int la=strlen(a+1),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);
f[i][j]=min(f[i][j],f[i-1][j-1]+(a[i]!=b[j]));
}
}
return f[la][lb];
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>(str[i]+1);//艹,我服了,我又服了,第一次见这样的二维数组,
//但其实也不能难理解,str[i]+1就是表示横坐标是i,纵坐标从1开始
//同理,如果输入str[i]的话,就是横坐标从i开始,纵坐标从0开始
}
while(m--)
{
int res=0;
char s[N];
int limit;
cin>>(s+1)>>limit;
for(int i=0;i<n;i++)
{
if(edit_distance(str[i],s)<=limit)
{
res++;
}
}
cout<<res<<endl;
}
return 0;
}
总结
提示:这里对文章进行总结:
DP问题,还是要掌握的多,见得多,多做题吧!