动态规划的练习题分为序列格子类的和背包类的
文章主要是序列格子类的问题,题目来自信息学奥数的题目,对于初学者比较友好
信息学奥数一本通的链接 信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
1.数字金字塔
//dp-xxxas1258-数字金字塔
/*
1.状态的定义和描述
dp[i][j] 从点(1,1)到点(i,j)途径数字最大
2.状态转移方程
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j];
3.找准边界
*/
#include<stdio.h>
int max(int a,int b);
int a[1001][1001];
int n;
int dp[1001][1001];
int maxx=0;
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
scanf("%d",&a[i][j]);
}
}
dp[1][1]=a[1][1];
for(int i=2;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j];
}
}
for(int i=1;i<=n;i++)
{
if(dp[n][i]>maxx)
maxx=dp[n][i];
}
printf("%d",maxx);
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
2.求最长不下降序列
注意审题,最长不下降序列意味着序列可以有相等的元素
//dp-xxxas1259-求最长不下降子序列
/*
1.状态的定义和描述
dp[i] 第i个元素最为结尾的最长不下降子序列
2.状态转移方程
if(a[i]>=a[j])
{
dp[i]=max(dp[i],dp[j]+1);
}
3.找准边界
若都不满足则它自身也是一个子序列,dp[i]=1;
*/
#include<stdio.h>
int max(int a,int b);
void print(int maxidex);
int n;
int a[201];
int dp[201];
int pre[201];
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp[i]=1;
}
int maxx=1;
int maxidex=1;
for(int i=2;i<=n;i++)//当i=2时,j往前找
{
for(int j=1;j<i;j++)
{
if(a[i]>=a[j])//这样就说明可以加在j后面
{
//还需要判断它是否大
if(dp[j]+1>dp[i])
{
pre[i]=j;//在i下标点的这个元素,它的前驱是j
dp[i]=dp[j]+1;
}
}
}
if(dp[i]>maxx)
{
maxx=dp[i];
maxidex=i;//记录最长不下降子序列的下标
}
}
printf("max=%d\n",maxx);
print(maxidex);
return 0;
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
void print(int maxidex)
{
if(maxidex==0)
return;
print(pre[maxidex]);//调用函数递归
printf("%d ",a[maxidex]);
}
3.拦截导弹
注意审题然后对应到相应的类型题目中去,这个题对应的就是最长上升子序列和最长下降子序列
//dp-xxxas1260-拦截导弹
/*
1.状态的定义和描述
dp_down[i] i下标元素为结尾的最长不上升子序列
dp_up[i] i下标元素为结尾的最长上升子序列
2.状态转移
if(a[i]<=a[j])//说明i下标元素大于j下标元素
{
dp_down[i]=max(dp_down[i],dp_down[j]+1);
}
else
{
dp_up[i]=max(dp_up[i],dp_up[j]+1);
}
3.找准边界
*/
#include<stdio.h>
int max(int a,int b);
int n=1;
int a[1001];
int dp_down[1001];
int dp_up[1001];
int main(void)
{
while(scanf("%d",&a[n])!=EOF)
{
dp_down[n]++;
dp_up[n]++;
n++;
}
n--;
int max_up=1,max_down=1;
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
{
dp_up[i]=max(dp_up[i],dp_up[j]+1);
}
else
{
dp_down[i]=max(dp_down[i],dp_down[j]+1);//上升子序列
}
}
if(dp_down[i]>max_down)
max_down=dp_down[i];
if(dp_up[i]>max_up)
max_up=dp_up[i];
}
printf("%d\n%d\n",max_down,max_up);
return 0;
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
4.城市交通路网
这个题相对复杂,分析题目从最后一个开始,找到每个元素到终点的最小值,到起点的时候就变遍历起点紧挨着的数,然后在查紧挨着的数到终点的距离
//dp-xxxas1261-城市交通路网
/*
1.状态的定义和描述
dp[i] i这个城市到指定的最后一个城市的最短路径
2.状态转移方程
dp[i]=min(dp[i],a[i][j]+dp[j]);
3.找准边界
当a[i][j]==0;的时候i到j这个城市是断开的,不相连的
*/
#include<stdio.h>
#define INF 0x3f3f3f3f
int min(int a,int b);
void print(int i);
int n;
int a[1001][1001];
int dp[1001];
int pre[1001];
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&a[i][j]);
}
dp[i]=INF;
}
dp[n]=0;
pre[n]=0;
for(int i=n-1;i>0;i--)//从最后一个元素开始
{
for(int j=i+1;j<=n;j++)
{
if(a[i][j]!=0&&a[i][j]+dp[j]<dp[i])
{
//dp[i]=min(dp[i],a[i][j]+dp[j]);
dp[i]=a[i][j]+dp[j];
pre[i]=j;
}
}
}
printf("minlong=%d\n",dp[1]);
print(1);
return 0;
}
int min(int a,int b)
{
if(a<b)
return a;
else
return b;
}
void print(int i)
{
printf("%d ",i);
if(pre[i]==0)
return ;
print(pre[i]);
}
5.挖地雷
和上一个题类似
//dp-xxxas1262-挖地雷
/*
1.状态描述以及定义
dp[i] 从i点到终点挖到的地雷最大值
2.状态转移
for(int j=1;j<=n;j++)
{
dp[i]=max(dp[i],dp[j]+a[i]);
}
3.找准边界
*/
#include<stdio.h>
int n;
int a[201];
int arr[201][201];
int dp[201];
int nestidex[201];
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp[i]=a[i];
}
int c,b;
while(scanf("%d %d",&c,&b)==2&&a!=0&&b!=0)
arr[c][b]=1;//等于1说明a到b可通
int maxx=0;
int maxindex=0;
for(int i=n-1;i>0;i--)
{
for(int j=i+1;j<=n;j++)
{
if(arr[i][j]==1&&dp[i]<dp[j]+a[i])
{
dp[i]=dp[j]+a[i];
nestidex[i]=j;//记录的是后驱,用递归打印
}
}
if(dp[i]>maxx)
{
maxx=dp[i];
maxindex=i;
}
}
while(nestidex[maxindex])
{
printf("%d-",maxindex);
maxindex=nestidex[maxindex];
}
printf("%d",maxindex);
printf("\n%d",maxx);
return 0;
}
6.友好城市
这题蛮有意思的,用笔依次画出来就能够找出规律,就会发现特别奇妙
//dp-xxxas1263-友好城市
/*
1.状态描述以及定义
dp[i] 以i点结尾的最长递增子序列
2.状态转移
for(int j=1;j<=n;j++)
{
dp[i]=max(dp[i],dp[j]+1);
}
3.找准边界
*/
#include<stdio.h>
void sort();
int max(int a,int b);
int n;
int a[5001];
int b[5001];
int dp[5001];
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&a[i],&b[i]);
dp[i]=1;
}
sort();
int maxx=0;
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(b[i]>b[j])
dp[i]=max(dp[i],dp[j]+1);
}
if(dp[i]>maxx)
maxx=dp[i];
}
printf("%d",maxx);
return 0;
}
void sort()
{
for(int i=1;i<n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(a[i]>a[j])
{
int temp=a[i];
a[i]=a[j];
a[j]=temp;
temp=b[i];
b[i]=b[j];
b[j]=temp;
}
}
}
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
7.合唱队形
这个题就是最长上升子序列的套牌,只是起点不同,根据题意需要从起点开始进行最长上升子序列的计算和从末尾开始的最长上升子序列的计算,然后从dp表中在找到最佳值
//dp-xxxas1264-合唱队形
/*
1.状态描述以及定义
dp_1_up[i] 从1这个点到i点结尾的最长上升子序列
dp_n_up[i] 从n这个点到i点结尾的最长上升子序列
2.状态转移
for(int j=1;j<=n;j++)
{
if(a[i]>a[j])
{
dp[i]=max(dp[i],dp[j]+1);
}
}
3.找准边界
dp描述的出发点不一样
*/
#include<stdio.h>
int max(int a,int b);
int n;
int a[101];
int dp_1_up[101];
int dp_n_up[101];
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp_1_up[i]=1;
dp_n_up[i]=1;
}
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
{
dp_1_up[i]=max(dp_1_up[i],dp_1_up[j]+1);
}
}
}
for(int i=n-1;i>0;i--)
{
for(int j=n;j>i;j--)
{
if(a[i]>a[j])
{
dp_n_up[i]=max(dp_n_up[i],dp_n_up[j]+1);
}
}
}
int maxx=0;
for(int i=1;i<=n;i++)
{
if(dp_n_up[i]+dp_1_up[i]-1>maxx)
maxx=dp_n_up[i]+dp_1_up[i]-1;
}
printf("%d",n-maxx);//表示最少要抽出去的人
return 0;
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
8.最长公共子序列
模板题目了
//dp-xxxas1265-最长公共子序列
/*
1.状态描述以及定义
dp[i][j] 字符串a[i] 下标i之前的字符串与 字符串b[j] 下标j之前的字符串的最长公共子序列
2.状态转移
if(a[i]==b[j])
{
dp[i][j]=dp[i-1][j-1]+1;
}
else
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
3.找准边界
边界是从0开始的
*/
#include<stdio.h>
#include<string.h>
int max(int a,int b);
char a[1001];
char b[1001];
int dp[1001][1001];
int main(void)
{
// gets(a);
// gets(b);
scanf("%s %s",a,b);
//初始化dp表
//a为行
int log=0;
for(int i=0;i<strlen(a);i++)
{
if(b[0]==a[i]&&log==0)
log=1;
if(log==1)
dp[0][i]=1;
else
dp[0][i]=0;
}
log=0;
for(int i=0;i<strlen(b);i++)
{
if(b[i]==a[0]&&log==0)
log=1;
if(log==1)
dp[i][0]=1;
else
dp[i][0]=0;
}
//dp操作
for(int i=1;i<strlen(b);i++)
{
for(int j=1;j<strlen(a);j++)
{
if(b[i]==a[j])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
printf("%d",dp[strlen(b)-1][strlen(a)-1]);
return 0;
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
9.机器分配
这个题蛮有意思的,对我来说理清楚还是花了一点时间的
//dp-xxxas1266-机器分配
/*
1.状态描述以及定义
dp[i][j] 第i个公司分配j个机器的最大收益
2.状态转移
dp[i][j]=max(dp[i][j],dp[i-1][j-k]+a[i][k])
3.找准边界
边界是从0开始的
*/
#include<stdio.h>
int max(int a,int b);
void print(int x,int y);
int m,n;//设备m台,公司n个
int a[16][16];
int dp[16][16];
int nestidex[16][16];//第i个公司在面临j台机器的时候选择了k台
int main(void)
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
for(int k=0;k<=j;k++)
{
//dp[i][j]=max(dp[i][j],dp[i-1][j-k]+a[i][k]);
/*a[i][k]可以理解为i公司选k=0时间就是不选,全部留给上一个公司,都是同时选择两个看看谁的大
若k=1,所以就要找到dp[i-1][j-1],找到上一个公司,比如说都选两个找到上一个公司选择一个的时候
然后比较大小,这个k的操作,我认为是一个经典,记住
*/
if(dp[i][j]<dp[i-1][j-k]+a[i][k])
{
dp[i][j]=dp[i-1][j-k]+a[i][k];
nestidex[i][j]=k;
//这里的标记数组nestidex[i][j]=k
}
}
}
}
printf("%d\n",dp[n][m]);
print(n,m);
return 0;
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
void print(int x,int y)
{
if(x==0) return ;
print(x-1,y-nestidex[x][y]);
printf("%d %d\n",x,nestidex[x][y]);
}
10.最长上升子序列
模板题
//dp-xxxas1267-最长上升子序列
/*
1.状态描述以及定义
dp[i] 以i结尾的最长上升子序列
2.状态转移
if(a[i]>a[j])
{
dp[i]=max(dp[i],dp[j]+1);
}
3.找准边界
*/
#include<stdio.h>
int max(int a,int b);
int n;
int a[1001];
int dp[1001];
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp[i]=1;
}
int maxx=0;
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
dp[i]=max(dp[i],dp[j]+1);
}
if(dp[i]>maxx)
maxx=dp[i];
}
printf("%d",maxx);
return 0;
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
11.最大子矩阵
这也是一个模板题,前缀和要好好理解
//dp-xxxas1282-最大子矩阵
/*
1.状态描述以及定义
dp[i] 最大字段和
2.状态转移
dp[i]=max(dp[i],dp[i-1]+dp[i]);
3.找准边界
*/
#include<stdio.h>
int max(int a,int b);
int n;
int a[101][101];
int dp[101];
int presum[101][101];//前缀和
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&a[i][j]);
presum[i][j]=presum[i-1][j]+a[i][j];
}
}
int maxx=a[1][1];
for(int x1=1;x1<=n;x1++)
{
for(int x2=x1;x2<=n;x2++)//上面两个都是控制行的,就是通过这个x1,x2来控制presum数组
{
for(int j=1;j<=n;j++)//操作合并列,进行降维
{
dp[j]=presum[x2][j]-presum[x1-1][j];
}
for(int i=1;i<=n;i++)
{
dp[i]=max(dp[i],dp[i-1]+dp[i]);//状态转移,要么自己成一个
maxx=max(maxx,dp[i]);
}
}
}
printf("%d",maxx);
return 0;
}
int max(int a,int b)
{
return (a>b)?a:b;
}
12.登山
这个题和上面的合唱类似
//dp-xxxas1283-登山
/*
1.状态描述以及定义
dp1_up[i] 起点从1开始,的最长上升子序列
dpn_up[i] 起点从n开始,的最长上升子序列
2.状态转移
if(a[i]>a[j])
{
dp[i]=max(dp[i],dp[j]+1);
}
3.找准边界
*/
#include<stdio.h>
int max(int a,int b);
int n;
int a[1001];
int dp1_up[1001];
int dpn_up[1001];
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp1_up[i]=1;
dpn_up[i]=1;
}
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
dp1_up[i]=max(dp1_up[i],dp1_up[j]+1);
}
}
for(int i=n-1;i>0;i--)
{
for(int j=n;j>i;j--)
{
if(a[i]>a[j])
dpn_up[i]=max(dpn_up[i],dpn_up[j]+1);
}
}
int maxx=0;
for(int i=1;i<=n;i++)
{
if(dp1_up[i]+dpn_up[i]-1>maxx)
maxx=dp1_up[i]+dpn_up[i]-1;
}
printf("%d",maxx);
return 0;
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
12.摘花生
走格子类问题
//dp-xxxas1284-摘花生
/*
1.状态描述以及定义
dp[i][j] 从起点到(i,j)点最大采摘花生数
2.状态转移
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+a[i][j];
3.找准边界
*/
#include<stdio.h>
int max(int a,int b);
int t;
int aa,bb;
int a[101][101];
int dp[101][101];
int main(void)
{
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&aa,&bb);//aa表示行,bb表示列
for(int i=1;i<=aa;i++)
{
for(int j=1;j<=bb;j++)
{
scanf("%d",&a[i][j]);
dp[i][j]=a[i][j];
}
}
for(int i=1;i<=aa;i++)
{
for(int j=1;j<=bb;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+a[i][j];
}
}
printf("%d\n",dp[aa][bb]);
}
return 0;
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
13.最大上升子序列和
模板题
//dp-xxxas1285-最大上升子序列和
/*
1.状态描述以及定义
dp[i] 以i点结尾的最大上升子序列和
2.状态转移
dp[i]=max(dp[i],dp[j]+a[i]);
3.找准边界
从i=1;开始
*/
#include<stdio.h>
int max(int a,int b);
int n;
int a[1001];
int dp[1001];
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp[i]=a[i];
}
int maxx=dp[0];
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
dp[i]=max(dp[i],dp[j]+a[i]);
}
if(dp[i]>maxx)
maxx=dp[i];
}
printf("%d",maxx);
return 0;
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
14.怪盗基德的滑翔翼
认真审题会发现就是模板题目变个描述方式
//dp-xxxas1286-怪盗基德的滑翔翼
/*
1.状态描述以及定义
dp1_up[i] 从1开始 以i作为结尾最大上升子序列
dpn_up[i] 从n开始 以i作为结尾最大上升子序列
2.状态转移
if(a[i]>a[j])
dp_down[i]=max(dp_down[i],dp_down[j]+1);
3.找准边界
*/
#include<stdio.h>
int max(int a,int b);
int t;
int n;
int a[101];
int dp1[101];
int dpn[101];
int main(void)
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp1[i]=1;
dpn[i]=1;
}
int max1=1;
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
dp1[i]=max(dp1[i],dp1[j]+1);
}
if(max1<dp1[i])
max1=dp1[i];
}
int maxn=1;
for(int i=n-1;i>0;i--)
{
for(int j=n;j>i;j--)
{
if(a[i]>a[j])
dpn[i]=max(dpn[i],dpn[j]+1);
}
if(dpn[i]>maxn)
maxn=dpn[i];
}
printf("%d\n",max(maxn,max1));
}
return 0;
}
int max(int a,int b)
{
return a>b?a:b;
}
15.最低通行费最低通行费
走格子问题
//dp-xxxas1287-最低通行费
/*
1.状态描述以及定义
dp[i][j] 从起点到[i][j]点的最大和
2.状态转移
dp[i][j]=min(dp[i][j-1],dp[i-1][j])+a[i][j];
3.找准边界
从i=1开始
求最小值要初始化边界,因为边界是0会影响最小值的判断
*/
#include<stdio.h>
int min(int a,int b);
int n;
int a[101][101];
int dp[101][101];
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&a[i][j]);
}
}
//初始化
dp[1][1]=a[1][1];
for(int i=2;i<=n;i++)//初始化行
dp[1][i]=dp[1][i-1]+a[1][i];
for(int i=2;i<=n;i++)//初始化列
dp[i][1]=dp[i-1][1]+a[i][1];
for(int i=2;i<=n;i++)
for(int j=2;j<=n;j++)
dp[i][j]=min(dp[i][j-1],dp[i-1][j])+a[i][j];
printf("%d",dp[n][n]);
return 0;
}
int min(int a,int b)
{
if(a>b)
return b;
else
return a;
}
16.三角形最佳路径问题
//dp-xxxas1288-三角形最佳路径问题
/*
1.状态描述以及定义
dp[i][j] 从起点到[i][j]点的最大和
2.状态转移
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j];
3.找准边界
从i=1开始
求最小值要初始化边界,因为边界是0会影响最小值的判断
*/
#include<stdio.h>
int max(int a,int b);
int n;
int a[101][101];
int dp[101][101];
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
scanf("%d",&a[i][j]);
int maxx=dp[1][1]=a[1][1];
for(int i=2;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j];
if(dp[i][j]>maxx)
maxx=dp[i][j];
}
}
printf("%d",maxx);
return 0;
}
int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
17.拦截导弹
//dp-xxxas1289-拦截导弹
/*
1.状态描述以及定义
dp_down[i] 以i为结尾的最长递减子序列 用于计算最大能够拦截的导弹
dp_up[i] 以i为结尾的最长递增子序列 用于计算系统数量
2.状态转移
if(a[i]<a[j])
dp_down[i]=max(dp_down[i],dp_down[j]+1);
if(a[i]>a[j])
dp_up[i]=max(dp_up[i],dp_up[j]+1);
3.找准边界
从i=1开始
求最小值要初始化边界,因为边界是0会影响最小值的判断
*/
#include<stdio.h>
int max(int a,int v);
int n;
int a[16];
int dp_down[16];
int main(void)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
dp_down[i]=1;
}
int maxdown=0;
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]<a[j])
dp_down[i]=max(dp_down[i],dp_down[j]+1);
}
if(dp_down[i]>maxdown)
maxdown=dp_down[i];
}
printf("%d",maxdown);
return 0;
}
int max(int a,int v)
{
if(a>v)
return a;
else
return v;
}