数字三角形
1.状态表示dp[i][j]
i.集合:所有从起点走到(i,j)的路径
ii.属性:Max
2.状态计算
为了方便计算,如果出现i-1这样的下标,最好i从1开始。
动态规划问题时间复杂度一般等于状态数量×转移计算量
做法:自顶向上
也可以自底向上做
#include <iostream>
#include <algorithm>
using namespace std;
const int INF=1e9;
const int N=510;
int num[N][N];
int dp[N][N];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
scanf("%d",&num[i][j]);
}
}
//初始化,相邻位置也要初始化
for(int i=1;i<=n;i++){
for(int j=0;j<=i+1;j++){
dp[i][j]=-INF;
}
}
//起点
dp[1][1]=num[1][1];
for(int i=2;i<=n;i++){//从2开始
for(int j=1;j<=i;j++){
dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+num[i][j];
}
}
int ans=-INF;
for(int i=1;i<=n;i++){
ans=max(dp[n][i],ans);
}
printf("%d",ans);
return 0;
}
最长上升子序列问题
动态规划问题中、
dp的维数越少越好
1.状态表示dp[N]
i.集合:以num[i]为结尾的最长上升子序列
ii.属性:集合里每一个上升子序列长度最大值
2.状态计算
分类:按第0~i-1个数中<num[i]的数分
dp[i]=max(dp[j],dp[i])+1
j∈(1,i-1)
最后遍历所有以i结尾最大值,找到其中最大的为最终结果
动态规划求方案:记录转移即可
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1010,INF=1e9;
int num[N];
int dp[N];
int g[N];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&num[i]);
dp[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(num[i]>num[j]){
if(dp[j]+1>dp[i]){
dp[i]=dp[j]+1;
g[i]=j;
}
}
}
}
printf("%d\n",g[3]);
int res=0,j;
for(int i=1;i<=n;i++){
if(dp[i]>res){
res=dp[i];
j=i;
}
}
printf("%d\n",res);
//输出序列
// while(g[j]!=0){
// printf("%d",num[j]);
// j=g[j];
// }
// printf("%d",num[j]);
for(int i=0,len=dp[j];i<len;i++){
printf("%d",num[j]);
j=g[j];
}
return 0;
}
优化:把不同长度的以i为结尾的最长上升子序列的值存下来,随序列长度增加,结尾的值一定是严格单调递增的
给定以ai为结尾的数,找到比ai小的最大的数
#include <iostream>
using namespace std;
const int N=1e5+10;
const int INF=0x3f3f3f3f;
int q[N];//长度为i的最长上升子序列中最后一位数的最小值
int a[N];//
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)scanf("%d",&a[i]);
q[0]=-INF;
int len=0;
for(int i=0;i<n;i++){
int l=0,r=len;
while(l<r){
int mid=l+r+1>>1;
if(q[mid]<a[i])l=mid;
else r=mid-1;
}
len=max(len,r+1);
q[r+1]=a[i];//q[r+1]一定大于a[i]
}
printf("%d",len);
return 0;
}
最长公共子序列
1.状态表示dp[N][N]
i.集合:在第一个序列的前i个字母中出现,在第二个序列的前j个子母中出现的子序列
ii.属性:max
2.状态计算
a[i]选或不选1/0
b[j]选或不选1/0
00
dp[i-1][j-1]
一般包含在01和10的情况中,不写
01
必须以子序列B[j]结尾,易简单理解为dp[i-1][j],但dp[i-1][j]不一定是以B[j]结尾的子序列,但一定包含01这种情况,我们可以用dp[i-1][j]表示这种情况,虽然有重叠,但没有关系,因为我们求的是最大值
求最大值最小值分成的子问题可以重复,求数量分成的子问题不能重复
10
必须以子序列A[i]结尾
11
必须包含A[i]和B[j]
dp[i-1][j-1]+1
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1010;
int dp[N][N];
char a[N],b[N];
int main(){
int n,m;
scanf("%d%d",&n,&m);
scanf("%s%s",a+1,b+1);//这里读入方式很神奇
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
if(a[i]==b[j])dp[i][j]=dp[i-1][j-1]+1;
}
}
printf("%d",dp[n][m]);
return 0;
}
最短编辑距离
#include <iostream>
using namespace std;
const int N=1010;
char A[N],B[N];
int dp[N][N];
int main(){
int n,m;
scanf("%d%s",&n,A+1);
scanf("%d%s",&m,B+1);
for(int i=1;i<=n;i++)dp[i][0]=i;//删除
for(int i=1;i<=m;i++)dp[0][i]=i;//添加
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1);
if(A[i]==B[j])dp[i][j]=min(dp[i-1][j-1],dp[i][j]);
else dp[i][j]=min(dp[i][j],dp[i-1][j-1]+1);//修改
}
}
printf("%d",dp[n][m]);
return 0;
}
编辑距离
#include <iostream>
#include <cstring>
using namespace std;
const int N=1010;
char A[N][10];
int dp[N][N];
int edited_distance(char str1[],char str2[]){
int len1=strlen(str1+1);
int len2=strlen(str2+1);
for(int i=1;i<=len1;i++)dp[i][0]=i;
for(int i=1;i<=len2;i++)dp[0][i]=i;
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+1;
if(str1[i]==str2[j])dp[i][j]=min(dp[i][j],dp[i-1][j-1]);
else dp[i][j]=min(dp[i][j],dp[i-1][j-1]+1);
}
}
return dp[len1][len2];
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%s",A[i]+1);
}
for(int i=1;i<=m;i++){
char B[N];
int t;
scanf("%s%d",B+1,&t);
int res=0;
for(int i=1;i<=n;i++){
if(t>=edited_distance(A[i],B)){
res++;
}
}
printf("%d\n",res);
}
return 0;
}