DP经典题

文章目录

  • 1.分苹果
  • 2.拼凑面额
  • 3.矩形覆盖
  • 4.连续子数组的最大和
  • 5.斐波那契凤尾
  • 6.最小邮票数
  • 7.神奇的口袋
  • 8.网格走法数目
  • 9.采药
  • 10.最大连续子序列
  • 11.最大上升子序列和

1.分苹果

n 只奶牛坐在一排,每个奶牛拥有 a i a_i ai 个苹果,现在你要在它们之间转移苹果,使得最后所有奶牛拥有的苹果数都相同,每一次,你只能从一只奶牛身上拿走恰好两个苹果到另一个奶牛上,问最少需要移动多少次可以平分苹果,如果方案不存在输出 -1。
输入描述:
每个输入包含一个测试用例。每个测试用例的第一行包含一个整数 n(1 <= n <= 100),接下来的一行包含 n 个整数 a i a_i ai(1 <= a i a_i ai <= 100)。
输出描述:
输出一行表示最少需要移动多少次可以平分苹果,如果方案不存在则输出 -1。
示例1
输入
4
7 15 9 5
输出
3

苹果总数不能被均分时,不成立
平均数减去每头牛的苹果数不是二的倍数时,不成立
最后只需要记录苹果数小于平均数的牛的移动次数即可

#include 
using namespace std;

int a[105];

int main(void)
{
	int n, ave = 0, ans = 0;
	cin >> n;
	for (int i = 1; i <= n; i++){
		cin >> a[i];
		ave += a[i];
	}
	bool flag = 1;
	if (ave % n != 0)
		flag = 0;
	else{
		ave /= n;
		for (int i = 1; i <= n; i++){
			int d = ave - a[i];
			if (d % 2 != 0)
				flag = 0;
			else if (d > 0)
				ans += d / 2;
		}
	}
	if (flag)
		cout << ans << endl;
	else
		cout << "-1" << endl;
	
	return 0;
}

2.拼凑面额

给你六种面额1、5、10、20、50、100元的纸币,假设每种币值的数量都足够多,编写程序求组成N元(N为0-10000的非负整数)的不同组合的个数。

链接:https://www.nowcoder.com/questionTerminal/14cf13771cd840849a402b848b5c1c93?orderByHotValue=0&commentTags=Python
来源:牛客网
输入描述:
输入为一个数字N,即需要拼凑的面额
输出描述:
输出也是一个数字,为组成N的组合个数。
示例1
输入
5
输出
2

完全背包求组合数问题,一般是将max改为相加即可。
当组成5000时,答案会超出int,需要设为long long。
dp[i][j]表示用前i种纸币来组成j,记得要将dp[0][0]初始化为1。

#include

int w[]={1,5,10,20,50,100};
long long dp[10][10005];

int main(void)
{
	int W;
	scanf("%d",&W);
	dp[0][0]=1;
	for(int i=0;i<6;i++)
		for(int j=0;j<=W;j++)
		{
			if(j<w[i])
				dp[i+1][j]=dp[i][j];
			else
				dp[i+1][j]=dp[i][j]+dp[i+1][j-w[i]];
		}
	printf("%lld\n",dp[6][W]);
	return 0;
}

3.矩形覆盖

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

链接:https://www.nowcoder.com/questionTerminal/72a5a919508a4251859fb2cfb987a0e6
来源:牛客网

在牛客上找到的题解,讲解的很清楚,这道题就是斐波那契。
DP经典题_第1张图片

4.连续子数组的最大和

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

链接:https://www.nowcoder.com/questionTerminal/459bd355da1549fa8a49e350bf3df484
来源:牛客网

class Solution {
public:
    int res = -0x3f3f3f3f, maxv = -0x3f3f3f3f;
    int FindGreatestSumOfSubArray(vector<int> array) {
        int n = array.size();
        for (int i = 0; i < n; i++){
            if (maxv >= 0)
                maxv += array[i];
            else
                maxv = array[i];
            res = max(res, maxv);
        }
        return res;
    }
};

5.斐波那契凤尾

NowCoder号称自己已经记住了1-100000之间所有的斐波那契数。
为了考验他,我们随便出一个数n,让他说出第n个斐波那契数。当然,斐波那契数会很大。因此,如果第n个斐波那契数不到6位,则说出该数;否则只说出最后6位。

链接:https://www.nowcoder.com/questionTerminal/c0a4b917a15f40a49ca10532ab9019fb?orderByHotValue=1&pos=1
来源:牛客网
输入描述:
输入有多组数据。每组数据一行,包含一个整数n (1≤n≤100000)。

输出描述:
对应每一组输入,输出第n个斐波那契数的最后6位。

示例1
输入
1234100000

输出
1235537501

WA到自闭的一道题,一看是斐波那契,就没仔细看,没想到这题暗藏玄机啊

#include

int n,t,arr[100005];

int main(void)
{
	arr[1]=1;arr[2]=2;
	for(int i=2;i<100000;i++)
	{
		t=arr[i];
		arr[i+1]=(arr[i]+arr[i-1])%1000000;
		arr[i]=t;
	}
	while(scanf("%d",&n)!=EOF)
	{
		if(n<29)
			printf("%d\n",arr[n]);
		else
			printf("%06d\n",arr[n]);
	}
	return 0;
}

6.最小邮票数

有若干张邮票,要求从中选取最少的邮票张数凑成一个给定的总值。 如,有1分,3分,3分,3分,4分五张邮票,要求凑成10分,则使用3张邮票:3分、3分、4分即可。

链接:https://www.nowcoder.com/questionTerminal/83800ae3292b4256b7349ded5f178dd1?pos=120&orderByHotValue=1
来源:牛客网
输入描述:
有多组数据,对于每组数据,首先是要求凑成的邮票总值M,M<100。然后是一个数N,N〈20,表示有N张邮票。接下来是N个正整数,分别表示这N张邮票的面值,且以升序排列。
输出描述:
对于每组数据,能够凑成总值M的最少邮票张数。若无解,输出0。

示例1
输入
10
5
1 3 3 3 4
输出
3

01背包问题的变形,把邮票的价值看作物品的重量,把物品的重量设为1,找最小的重量。

#include
#include
#include
using namespace std;
const int INF=0x3f3f3f3f;

int main(void)
{
	int M,N;
	int w[25],v[25]; 
	int dp[25][105];
	while(scanf("%d",&M)!=EOF)
	{
		scanf("%d",&N);
		for(int i=0;i<N;i++)
		{
			scanf("%d",&w[i]);
			v[i]=1;
		}
		memset(dp[0],INF,sizeof dp[0]);
		dp[0][0]=0;
		for(int i=0;i<N;i++)
			for(int j=0;j<=M;j++)
			{
				if(j<w[i])
					dp[i+1][j]=dp[i][j];
				else
					dp[i+1][j]=min(dp[i][j],dp[i][j-w[i]]+v[i]);
			}
		if(dp[N][M]==INF)
			printf("0\n");
		else
			printf("%d\n",dp[N][M]);
	}
	return 0;
}

7.神奇的口袋

有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是 a 1 , a 2 … … a n a_1,a_2……a_n a1a2an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。

链接:https://www.nowcoder.com/questionTerminal/9aaea0b82623466a8b29a9f1a00b5d35?toCommentId=1467409
来源:牛客网
输入描述:
输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的数目。接下来的n行,每行有一个1到40之间的正整数,分别给出a1,a2……an的值。
输出描述:
输出不同的选择物品的方式的数目。
示例1
输入
3
20
20
20
输出
3

01背包

#include
#include
using namespace std;

int w[25];
long long dp[25][45];

int main(void)
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
		scanf("%d",&w[i]);
	dp[0][0]=1;
	for(int i=0;i<n;i++)
		for(int j=0;j<=40;j++)
		{
			if(j<w[i])
				dp[i+1][j]=dp[i][j];
			else
				dp[i+1][j]=dp[i][j]+dp[i][j-w[i]];
		}
	printf("%lld\n",dp[n][40]);
	return 0;
}

8.网格走法数目

有一个X*Y的网格,小团要在此网格上从左上角到右下角,只能走格点且只能向右或向下走。请设计一个算法,计算小团有多少种走法。给定两个正整数int x,int y,请返回小团的走法数目。

链接:https://www.nowcoder.com/questionTerminal/e27b9fc9c0ec4a17a5064fb6f5ab13e4?orderByHotValue=1&page=1&onlyReference=false
来源:牛客网
输入描述:
输入包括一行,逗号隔开的两个正整数x和y,取值范围[1,10]。
输出描述:
输出包括一行,为走法的数目。
示例1
输入
3 2
输出
10

如果按排列组合做的话就直接 C x + y x C_{x+y}^{x} Cx+yx,需要注意x=y=9时,会爆掉int,需要开成long long。

#include

void C(int n,int m)
{
	long long ans=1;
	for(int i=n;i>n-m;i--)
		ans*=i;
	for(int i=2;i<=m;i++)
		ans/=i;
	printf("%lld\n",ans);
}

int main(void)
{
	int x,y;
	scanf("%d%d",&x,&y);
	C(x+y,x);
	return 0;
}

如果用dp做的话,先设好边界条件,再进行状态转移即可。
dp[i][j]表示到达这个点有几种走法,dp[i][j] = dp[i - 1][j] + dp[i][j - 1]。

#include
#include

int dp[15][15];

int main(void)
{
	int x,y;
	scanf("%d%d",&x,&y);
	for(int i=0;i<=10;i++)
		dp[i][0]=1;
	for(int j=0;j<=10;j++)
		dp[0][j]=1;
	for(int i=1;i<=10;i++)
		for(int j=1;j<=10;j++)
			dp[i][j]=dp[i-1][j]+dp[i][j-1];
	printf("%d\n",dp[x][y]);
	return 0;
}

这道题说的是网格,注意区分网格和其他的

#include 
#include 
#include 
using namespace std;

int dp[15][15];

int main(void)
{
	int x, y;
	cin >> x >> y;
	dp[1][1] = 1;//初始状态
	for (int i = 1; i <= x + 1; i++)
		for (int j = 1; j <= y + 1; j++)
			dp[i][j] += dp[i - 1][j] + dp[i][j - 1];
	cout << dp[x + 1][y + 1] << endl;
	
	return 0;
}

9.采药

辰辰是个很有潜能、天资聪颖的孩子,他的梦想是称为世界上最伟大的医师。 为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。 医师把他带到个到处都是草药的山洞里对他说: “孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。 我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。” 如果你是辰辰,你能完成这个任务吗?

链接:https://www.nowcoder.com/questionTerminal/d7c03b114f0541dd8e32ce9987326c16?orderByHotValue=2&done=0&pos=1&onlyReference=false
来源:牛客网
输入描述:
输入的第一行有两个整数T(1 <= T <= 1000)和M(1 <= M <= 100),T代表总共能够用来采药的时间,M代表山洞里的草药的数目。
接下来的M行每行包括两个在1到100之间(包括1和100)的的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出描述:
可能有多组测试数据,对于每组数据,
输出只包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
示例1
输入
70 3
71 100
69 1
1 2
输出
3

01背包

#include
#include
#include
using namespace std;

int v[105],w[105];
int dp[105][1005];

int main(void)
{
	int T,M;
	while(scanf("%d%d",&T,&M)!=EOF)
	{
		for(int i=0;i<M;i++)
			scanf("%d%d",&w[i],&v[i]);
		for(int j=0;j<=100;j++)
			dp[0][j]=0;
		for(int i=0;i<M;i++)
			for(int j=0;j<=T;j++)
			{
				if(j<w[i])
					dp[i+1][j]=dp[i][j];
				else
					dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
			}
		printf("%d\n",dp[M][T]);
	}
	return 0;
}

10.最大连续子序列

给定K个整数的序列{ N 1 , N 2 , . . . , N K N_1, N_2, ..., N_K N1,N2,...,NK },其任意连续子序列可表示为{ N i , N i + 1 , . . . , N j N_i, N_{i+1}, ..., N_j Ni,Ni+1,...,Nj },其中 1 <= i <= j <= K。最大连续子序列是所有连续子序列中元素和最大的一个,例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和为20。现在增加一个要求,即还需要输出该子序列的第一个和最后一个元素。

链接:https://www.nowcoder.com/questionTerminal/afe7c043f0644f60af98a0fba61af8e7
来源:牛客网
输入描述:
测试输入包含若干测试用例,每个测试用例占2行,第1行给出正整数K( K< 10000 ),第2行给出K个整数,中间用空格分隔。当K为0时,输入结束,该用例不被处理。
输出描述:
对每个测试用例,在1行里输出最大和、最大连续子序列的第一个和最后一个元素,中间用空格分隔。如果最大连续子序列不唯一,则输出序号i和j最小的那个(如输入样例的第2、3组)。若所有K个元素都是负数,则定义其最大和为0,输出整个序列的首尾元素。
示例1
输入
6
-2 11 -4 13 -5 -2
10
-10 1 2 3 4 -5 -23 3 7 -21
6
5 -8 3 2 5 0
1
10
3
-1 -5 -2
3
-1 0 -2
0
输出
20 11 13
10 1 4
10 3 5
10 10 10
0 -1 -2<——>原题这里有误,应该是-1 -1 -1
0 0 0

在求最大连续子序列和的基础上增加了输出子序列的首尾元素。
我开了一个first[10005]数组来记录以对应的数结尾的子列首元素,如果dp[i - 1] < 0,那么以arr[i]结尾的最大连续子序列就是arr[i]自己,所以此时first[i] = arr[i]。否则,first[i] = first[i-1],首元素不变。
又使用了一个变量end来记录最大连续子序列的下标。

#include
#include
using namespace std;

int main(void)
{
	int k,ans,end;
	int arr[10005],dp[10005],first[10005];
	while(scanf("%d",&k)!=EOF&&k)
	{
		for(int i=0;i<k;i++)
			scanf("%d",&arr[i]);
		dp[0]=arr[0];
		ans=dp[0];end=0;
		first[0]=arr[0];
		for(int i=1;i<k;i++)
		{
			if(dp[i-1]<0)//这时需要把first设为当前数 
				first[i]=arr[i];
			else//否则和上一个相同 
				first[i]=first[i-1]; 
			dp[i]=max(arr[i],dp[i-1]+arr[i]);
			if(ans<dp[i])
				end=i;
			ans=max(ans,dp[i]);
		}
		printf("%d %d %d\n",ans,first[end],arr[end]);
	}
	return 0;
}

11.最大上升子序列和

一个数的序列 b i b_i bi,当 b 1 < b 2 < . . . < b S b_1 < b_2 < ... < b_S b1<b2<...<bS的时候,我们称这个序列是上升的。对于给定的一个序列( a 1 , a 2 , . . . , a N a_1, a_2, ...,a_N a1,a2,...,aN),我们可以得到一些上升的子序列( a i 1 , a i 2 , . . . , a i K a_{i_1}, a_{i_2}, ..., a_{i_K} ai1,ai2,...,aiK),这里 1 1 1 <= i 1 i_1 i1 < i 2 i_2 i2 < … < i K i_K iK <= N N N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中序列和最大为18,为子序列(1, 3, 5, 9)的和. 你的任务,就是对于给定的序列,求出最大上升子序列和。注意,最长的上升子序列的和不一定是最大的,比如序列(100, 1, 2, 3)的最大上升子序列和为100,而最长上升子序列为(1, 2, 3)。

链接:https://www.nowcoder.com/questionTerminal/dcb97b18715141599b64dbdb8cdea3bd
来源:牛客网
输入描述:
输入包含多组测试数据。
每组测试数据由两行组成。第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000(可能重复)。
输出描述:
对于每组测试数据,输出其最大上升子序列和。
示例1
输入
7
1 7 3 5 9 4 8
输出
18

最长上升子序列的变形
dp[i]的初始值应设为对应的元素值arr[i],转移方程也变为dp[i] = max(dp[i], dp[j] + arr[i])

#include
#include
#include
using namespace std;

int main(void)
{
	int n;
	int arr[1005],dp[1005];
	while(scanf("%d",&n)!=EOF)
	{
		for(int i=0;i<n;i++)
			scanf("%d",&arr[i]);
		int ans=0;
		for(int i=0;i<n;i++)
		{
			dp[i]=arr[i];
			for(int j=0;j<i;j++)
			{
				if(arr[j]<arr[i])
					dp[i]=max(dp[i],dp[j]+arr[i]);
			}
			ans=max(ans,dp[i]); 
		}
		printf("%d\n",ans);
	}
	return 0;
}

你可能感兴趣的:(#,专题十二,基础DP)