DP小小总结

先贴上题目练习
密码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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值