先贴上题目练习
密码20210401
快乐的 DP之旅开始了~
M Q S V 我实在看不明白,信女愿认真学习两天求一大佬讲解!
A
基础方程:dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+a[i][j];
这样是考虑[i][j]位置可由哪个位置过来
B
巨巨巨坑!看题容易漏条件:
如果当前格子是(x,y),下一步可以是(x+1,y),(x,y+1)或者(x,y*k)其中k>1。
/*
前提dp[i][j]=-inf;定义所有为负值
因为现在考虑是[i][j]可以到哪里,到的位置初始值-inf;
*/
dp[1][1]=a[1][1];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
dp[i+1][j]=max(dp[i+1][j],dp[i][j]+a[i+1][j]);
dp[i][j+1]=max(dp[i][j+1],dp[i][j]+a[i][j+1]);
for(int k=2;k*j<=m;k++)//每次循环求k倍
{
dp[i][j*k]=max(dp[i][j*k],dp[i][j]+a[i][j*k]);
}
}
}
C dp
推荐指数:*****
题意:n,n*n矩阵,找一条路径,使此路径乘积和尾0个数最少;
初始思路:
1.不存在0,求出矩阵因子2和5的最小个数的路径;
2.存在0:
(1)、存在个别0,但2或5个数<1,此时尾0个数为0,路径绕开0;
(2)、存在个别0,但2和5个数=1,此时尾0个数为1,路径可经过0,可绕开走2、5都行;
(3)、存在0区域带将起终点隔离开,每条路径一定经过0,此时尾0个数为1;
没想出来怎么判断2.(2)!
最后借鉴了别人代码,dp鲨我,呜呜呜。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<string.h>
#include<string>
#include<cstring>
using namespace std;
typedef long long ll;
//判断某因子个数
int get(int num,int base)
{
if (num == 0)return 1;//若为0,默认2*5,一个2一个5;
int ret = 0;
while (num%base == 0)
{
num /= base;
ret++;
}
return ret;
}
int mp[1010][1010][2];//记录对应因子2、5的个数
int dp[1010][1010][2];//记录到此位置最小2、5的个数
char dir[1010][1010][2];//记录从哪个方向来
char path[2010];//记录路径
int pflag;//路标
int main()
{
int n;
while (~scanf("%d",&n))
{
int a,sx, sy;
int flag = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
scanf("%d",&a);
if (a == 0)
{//标记0的位置
flag = 1;
sx = i;
sy = j;
}
mp[i][j][0] = get(a, 2);
mp[i][j][1] = get(a, 5);
}
}
memset(dp, 0, sizeof(dp));
dp[1][1][0] = mp[1][1][0];
dp[1][1][1] = mp[1][1][1];
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
for (int k = 0; k < 2; k++)
{
if (i == 1 && j == 1)continue;
if (i == 1)
{
dp[i][j][k] = dp[i][j - 1][k] + mp[i][j][k];//第一行只能前一个过来
dir[i][j][k] = 'R';
}
else if (j == 1)
{
dp[i][j][k] = dp[i - 1][j][k] + mp[i][j][k];//第一列只能由上一列推下来
dir[i][j][k] = 'D';
}
else
{
dp[i][j][k] = min(dp[i - 1][j][k], dp[i][j - 1][k]) + mp[i][j][k];//从左或上推下来
dir[i][j][k] = dp[i - 1][j][k] < dp[i][j - 1][k] ? 'D' : 'R';//存
}
}
}
}
if (flag && min(dp[n][n][0], dp[n][n][1]) > 1)//2、5个数>1,否则最小尾0尾数为0;初始思路的其余情况都可归为第二类;
{
printf("1\n");
for (int i = 1; i < sy; i++)
printf("R");
for (int j = 1; j < n; j++)
printf("D");
for (int i = sy+1; i <= n; i++)
printf("R");
}
else
{
printf("%d\n",min(dp[n][n][0], dp[n][n][1]));//贴图解释,绕进去好几次;
int k = 0;
if (dp[n][n][0] > dp[n][n][1])
k = 1;
pflag = 0;
for (int i = n, j = n; i != 1 || j != 1;)
{
path[pflag] = dir[i][j][k];
// cout<<pflag<<"--"<<path[pflag]<<'\n';
pflag++;
if (dir[i][j][k] == 'D')i--;
else j--;
}
for (int i = pflag-1; i >= 0; i--)
printf("%c",path[i]);
}
printf("\n");
}
return 0;
}
/*
3
0 0 0
0 0 0
0 0 0
3
1 2 3
4 5 6
7 8 9
3
2 2 2
2 0 2
2 2 2
3
2 2 0
2 0 2
0 2 2
*/
D 最长上升子序列
//dp[i]=1;最初每个对应1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
if(a[j]<a[i])
dp[i]=max(dp[i],dp[j]+1);
}
}
E 最长递增子序列长度 + 二分优化
推荐指数:*****
题意:求最长递增子序列长度!数据太大需要优化!!!
TLE版本
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[100010];
int dp[100010];
int main()
{
int n;
while (~scanf("%d",&n))
{
for (int i = 1;i <= n; i++)
scanf("%d",&a[i]);
for (int i = 1; i <= n; i++)
{
dp[i] = 1;
for (int j = 1; j < i; j++)
if (a[j] < a[i])
dp[i] = max(dp[j]+1, dp[i]);
}
int maxx = 0;
for (int i = 1; i <= n; i++)
maxx = max(maxx, dp[i]);
printf("%d\n",maxx);
}
return 0;
}
优化版本
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[100010];
int dp[100010];
int main()
{
int n;
while (~scanf("%d",&n))
{
for (int i = 1;i <= n; i++)
scanf("%d",&a[i]);
//dp[]存每一长度时对应的最小值
/*
eg1:8 3 9 10
dp[1]=8 //子序列长度为1
dp[1]=8or3 应存dp[1]=3
dp[2]=9//子序列长度为2
dp[3]=10//子序列长度为3
假设加数据4 dp[2]=9更新为dp[2]=4 //长度为2时3 9或者3 4 应存3 4 此时dp[]=3 4 10;
假设加数据7 dp[3]=10更新为dp[3]=7 //长度为3时3 4 10或者3 4 7 应存3 4 7 此时dp[]=3 4 7;
假设加数据11 更新长度++,此时dp[]=3 4 7 11;
eg2:1 7 8 4
1 7 8 -> 1 4 8//长度为2时 1 7 or 1 4 应存1 4
假设加数据5 ->8更新为5 此时 1 4 5
*/
dp[1]=a[1];
int f=1;
int left,right;
int mid;
for (int i = 2; i <= n; i++)
{
if(dp[f]<a[i])dp[++f]=a[i];
else
{
left=1;//二分
right=f;
while(left<=right)//<=确定的为left ,<确定的为right
{
mid=(left+right)/2;
if(dp[mid]<a[i])
left=mid+1;
else if(dp[mid]>a[i])
right=mid-1;
else if(dp[mid]==a[i])
{
left=mid;
break;
}
}
dp[left]=a[i];
}
}
printf("%d\n",f);
}
return 0;
}
F
题意:n行人,身高先增后减,最少挑出去多少人
思路:正反向最大递增序列or貌似正向递增+递减最大值也可
1.a[]正存,b[]逆存,dp1[]正增,dp2[]逆增
n-max(dp1[i]+dp2[i])-1;//-1 中间最高的算了两遍
os:不想贴代码了,把D正反鞭尸就行
G 最少拦截系统
题意:导弹拦截系统:它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高度.
面对导弹,最少搞几套导弹拦截系统。
思路:
int main()
{
int n;
while(cin>>n)
{
memset(a,0,sizeof(a));
memset(dp1,0,sizeof(dp1));
for(int i=1;i<=n;i++)
cin>>a[i];
dp1[1]=a[1];
int num=1;
for(int i=1;i<=n;i++)
{
int j;
for( j=1;j<=i;j++)
{
if(a[i]<=dp1[j])
{
dp1[j]=a[i];
break;
}
}
if(j>i)
dp1[++num]=a[i];
}
cout<<num<<'\n';
}
return 0;
}
H 最大上升子序列和
for(int i=1;i<=n;i++)
{
dp[i]=a[i];
for(int j=1;j<i;j++)
{
if(a[j]<a[i])
dp[i]=max(dp[j]+a[i],dp[i]);
}
}
int maxx=0;
for(int i=1;i<=n;i++)
{
maxx=max(maxx,dp[i]);
}
printf("%d\n",maxx);
I 最长公共子序列
//dp好难想!
#include <iostream>
#include<cmath>
#include <cstdio>
#include <algorithm>
#include<string>
#include<string.h>
#include<set>
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 5;
int dp[maxn][maxn];
int main()
{
string s1, s2;
while (cin >> s1 >> s2)
{
int len1 = s1.length();
int len2 = s2.length();
for (int i = 0; i <= len1; i++)
{//i=len1,j=len2;末状态;
for (int j = 0; j <= len2; j++)
{
if (i == 0 || j == 0)
{
dp[i][j] = 0;
continue;
}
if (s1[i-1] == s2[j-1])
dp[i][j] = dp[i - 1][j - 1] + 1;//so这里对应[i-1][j-1];
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
cout << dp[len1][len2]<<'\n';
}
return 0;
}
/*
*/
J 最大连续子序列和
推荐指数:***
题意:给一组数,全小于零输出0,else 求最大连续子序列和,并输出该子序列头尾元素。
我相信聪明的你可以看懂代码
os:我不想写了
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
typedef long long ll;
int a[10010];
int dp[10010];
int start[10010];
int endd[10010];
int max(int x,int y)
{
if(x>y)return x;
else return y;
}
int main()
{
int n;
while(~scanf("%d", &n)&&n)
{
memset(a,0,sizeof(a));
memset(dp,0,sizeof(dp));
memset(start,0,sizeof(start));
memset(endd,0,sizeof(endd));
int f=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]>=0)f=1;
}
if(f==0)
{
printf("0 %d %d\n",a[1],a[n]);
continue;
}
int num1=1;
int num2=1;
dp[0]=-1;
for(int i=1;i<=n;i++)
{
dp[i]=max(a[i],dp[i-1]+a[i]);
if(dp[i-1]+a[i]<a[i])
{
start[num1]=a[i];
endd[num2]=a[i];
}
else
{
start[num1]=start[num1-1];
endd[num2]=a[i];
}
num1++;
num2++;
}
int ff;
int maxx=-1;
for(int i=1;i<=n;i++)
{
if(maxx<dp[i])
{
maxx=dp[i];
ff=i;
}
}
printf("%d %d %d\n",maxx,start[ff],endd[ff]);
}
return 0;
}
K 简单bfs
int f[110][110];
int dx[]={-1,0,0,1};
int dy[]={0,-1,1,0};
int bfs(int x,int y)
{
if(~f[x][y])return f[x][y];
f[x][y]=1;
for(int i=0;i<4;i++)
{
int xx=x+dx[i];
int yy=y+dy[i];
if(xx<1||xx>r||yy<1||yy>c)continue;
if(a[xx][yy]<a[x][y])
f[x][y]=max(f[x][y],bfs(xx,yy)+1);
}
return f[x][y];
}
L 规律
半天没心思过来和dp有什么关系,搜了一下,发现可以规律解决,哈哈哈哈,那我势必不会用dp。
M
N
题意:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210421210318416.png?x-oss-
思路:从最后的时间状态往前推;每个的dp[i][j]=max(dp[i+1][j+1],dp[i+1][j],dp[i+1][j-1]);//i指时间,j指位置;
#include <cstdio>
#include<string.h>
#include<iostream>
using namespace std;
#define maxn 200010
int dp[maxn][20];
int max(int a,int b)
{
if(a>b)return a;
else return b;
}
int main()
{
int n;
while(scanf("%d",&n)!=EOF&&n)
{
memset(dp,0,sizeof(dp));
int pos,time;
int maxx=0;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&pos,&time);
dp[time][pos]++;//初始状态
maxx=max(time,maxx);
}
for(int i=maxx;i>=0;i--)
for(int j=0;j<11;j++)
dp[i][j]+=max(dp[i+1][j+1],max(dp[i+1][j],dp[i+1][j-1]));
printf("%d\n",dp[0][5]);//初始位置
}
return 0;
}
O hdu1260
题意:一工具人售票,有n组,每组k人,k个数据表示第k人买票时间,k-1个数据表示相邻两人一起购票时间,工具人早八点上班,求工具人最早下班时间。
思路:每个人自己购票(=到i-1人时的时间+t1[i])或和前一人一起购票(=到i-2个人时的时间+t2[i-1])//t2[i-1]因为i对应i-1;
转移方程dp[i]=min(dp[i-1]+t1[i],dp[i-2]+t2[i-1]);i>=2;
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
int dp[2005];
int a[2005],b[2005];
int main()
{
int n;
cin>>n;
while(n--)
{
memset(dp,0,sizeof(dp));
int k;
cin>>k;
for(int i=1;i<=k;i++)
cin>>a[i];
for(int i=1;i<=k-1;i++)
cin>>b[i];
dp[1]=a[1];
for(int i=2;i<=k;i++)
{
dp[i]=min(dp[i-1]+a[i],dp[i-2]+b[i-1]);
}
//cout<<dp[k]<<'\n';
int ans=dp[k];
int a1,a2,a3;
a1=ans%60;
ans/=60;
a2=ans%60;
ans/=60;
a3=ans%60+8;
if(a3<10)cout<<"0"<<a3<<":";
else cout<<a3<<":";
if(a2==0)cout<<"00"<<":";
else if(a2<10)cout<<"0"<<a2<<":";
else cout<<a2<<":";
if(a1==0)cout<<"00"<<" am"<<'\n';
else if(a1<10)cout<<"0"<<a1<<" am"<<'\n';
else cout<<a1<<" am"<<'\n';
}
return 0;
}
P 完全背包
题意:T组数据,每组数据一头空猪和一头装满硬币的猪的重量E和F,N个数据,P价值W重量,求存钱罐最低金额。
memset(bag, 0x3f3f3f3f, sizeof(bag));
bag[0] = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= ans; j++)
{
if (j >= ww[i])
bag[j] = min(bag[j], bag[j -ww[i]] + vv[i]);
}
}
if(bag[ans]< 0x3f3f3f3f)
cout <<"The minimum amount of money in the piggy-bank is "<< bag[ans] <<"."<< '\n';
else cout << "This is impossible." << '\n';
**R **
题意:一堆老鼠wight和speed,求w增s减的最大集合并打印。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 100010
using namespace std;
int b[1010];
struct mice
{
int w;
int s;
int pos;
int way;
}a[1010];
int dp[1010];
int max(int x, int y)
{
if (x > y)return x;
else return y;
}
bool cmp(mice x,mice y)
{
if(x.w!=y.w)return x.w < y.w;
else return x.s > y.s;
}
int main()
{
int num = 1;
while (cin >> a[num].w >> a[num].s)
{
a[num].pos = num;
a[num].way = 0;
num++;
}
if (num == 1)
{
cout << "0" << '\n';
return 0;
}
sort(a + 1, a + num, cmp);
dp[1] = 1;
for (int i = 1; i < num; i++)
{
dp[i] = 1;
for (int j = 1; j < i; j++)
{
if(a[j].s>a[i].s&&a[j].w<a[i].w)
if (dp[j] + 1 > dp[i])
{
dp[i] = dp[j] + 1;
a[i].way = j;
}
}
}
int maxx = 0;
int flag;
for (int i = 1; i < num; i++)
{
if (maxx < dp[i])
{
maxx = dp[i];
flag = i;
}
}
//cout << "f" <<flag<< '\n';
int wz = 1;
while (a[flag].way)
{
b[wz++] = a[flag].pos;
flag = a[flag].way;
}
cout << maxx << '\n';
cout << a[flag].pos << '\n';
for (int i = wz - 1; i > 0; i--)
{
cout << b[i] << '\n';
}//倒着输出 路径倒入
return 0;
}
T 最长上升子序列
同D