练习Day——15(动态规划)

动态规划

目录

  • 动态规划
    • 01背包问题
    • 装箱问题
    • 方格取数
    • 传纸条
    • 数的划分
    • 传球游戏
    • 入学考试
    • 开心的金明

01背包问题

题目描述
一个旅行者有一个最多能装 M 公斤的背包,现在有 n 件物品,它们的重量分别是W1,W2,…,Wn,它们的价值分别为C1,C2,…,Cn,求旅行者能获得最大总价值。

输入
第一行:两个整数,M(背包容量,M≤200)和N(物品数量,N≤30);

第2…N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。

输出
仅一行,一个数,表示最大总价值。

样例输入
10 4
2 1
3 3
4 5
7 9

样例输出
12

题目解析
声明一个大小为dp[n][m]的二维数组,dp[i][j]表示在要装第i件物品且背包容量为j时的最大价值。
因此,可以分为几种情况讨论:
(1)j (2)j>=w[i]时,背包可以放下当前物品,那么需要考虑:1、如果拿了,dp[i][j]=dp[i-1][j-w[i]]+c[i];
2、如果不拿,dp[i][j]=dp[i-1][j];
需要比较这两种情况哪个能获得更大的价值,即选择Math.max(dp[i-1][j-w[i]]+c[i],dp[i-1][j]);

import java.util.Scanner;

public class Main {
     
	public static void main(String[]args) {
     
		Scanner scan=new Scanner(System.in);
		int m,n;
		m=scan.nextInt();//背包容量
		n=scan.nextInt();//物品数量
		int w[]=new int[m+1];//重量
		int c[]=new int[n+1];//价值
		for(int i=1;i<=n;i++) {
     
			w[i]=scan.nextInt();
			c[i]=scan.nextInt();
		}
		scan.close();
		
		int dp[][]=new int[n+1][m+1];
		for(int i=1;i<=n;i++) {
     
			for(int j=1;j<=m;j++) {
     
				if(j>=w[i])
					dp[i][j]=Math.max(dp[i-1][j-w[i]]+c[i],dp[i-1][j]);
				else
					dp[i][j]=dp[i-1][j];
			}
			
		}
		
		System.out.println(dp[n][m]);
	}

}

装箱问题

题目描述
有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30),每个物品有一个体积(正整数)。
要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。

输入
第一行为一个整数,表示箱子容量;
第二行为一个整数,表示有n个物品;
接下来n行,每行一个整数表示这n个物品的各自体积。

输出
一个整数,表示箱子剩余空间。

样例输入
24
6
8
3
12
7
9
7

样例输出
0

import java.util.Scanner;

//装箱问题
public class Main {
     
	public static void main(String[]args) {
     
		Scanner scan=new Scanner(System.in);
		int v=scan.nextInt();//箱子容量
		int n=scan.nextInt();//n个物品
		int num[]=new int[n+1];//n个物品各自体积
		for(int i=1;i<=n;i++)
			num[i]=scan.nextInt();
		scan.close();
		
		int res=1000000;
		int dp[][]=new int [n+1][v+1];
		for(int i=1;i<=n;i++) {
     
			for(int j=1;j<=v;j++) {
     
				if(j>=num[i])
					dp[i][j]=Math.max(dp[i-1][j], dp[i-1][j-num[i]]+num[i]);
				else
					dp[i][j]=dp[i-1][j];
			}
			if(res>v-dp[i][v])
				res=v-dp[i][v];
		}
		
		System.out.println(res);
	}

}

方格取数

题目描述
设有N*N的方格图(N< =10),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。
某人从图的左上角的A 点(1,1)出发,可以向下行走,也可以向右走,直到到达右下角的B点(N,N)。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从A点到B 点共走两次,试找出2条这样的路径,使得取得的数之和为最大。

输入
输入的第一行为一个整数N(表示N*N的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的0表示输入结束。

输出
只需输出一个整数,表示2条路径上取得的最大的和。

样例输入
8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0

样例输出
67

题目解析
四维dp。假设有两个人在方格中走,dp[i][j][k][l]表示当第一个人走到坐标(i,j),第二个人走到坐标(k,l)时取得的最大值的和。一个人走到(i,j)有两种方式,一种是向下行走,从(i,j-1)走到的;另一种是向右行走,从(i-1,j)走到的。因此两个人就拥有四种情况,要取这四种情况中的最大值。
注意:当两个人同时走到一个格子时,格子的值只能加一次。

import java.util.Scanner;

//方格取数
public class Main {
     
	public static void main(String[]args) {
     
		Scanner scan=new Scanner(System.in);
		int n=scan.nextInt();//方格大小
		int square[][]=new int [n+1][n+1];
		int a,b,c;
		while(true) {
     
			a=scan.nextInt();
			b=scan.nextInt();
			c=scan.nextInt();
			if(a==0 && b==0 && c==0)
				break;
			else
			    square[a][b]=c;
		}
		scan.close();
		int dp[][][][]=new int[n+1][n+1][n+1][n+1];
		for(int i=1;i<=n;i++) {
     
			for(int j=1;j<=n;j++) {
     
				for(int k=1;k<=n;k++) {
     
					for(int l=1;l<=n;l++) {
     
						//两个人的四种运动情况
						int t1=Math.max(dp[i-1][j][k-1][l], dp[i-1][j][k][l-1]);
						int t2=Math.max(dp[i][j-1][k][l-1], dp[i][j-1][k-1][l]);
						int t3=Math.max(t1, t2);
						if(i!=k && j!=l)//如果坐标不相等,即两人不在同一位置,则都加进去
							dp[i][j][k][l]=t3+square[i][j]+square[k][l];
						else//如果坐标相等,即两人在同一位置,则只加一次
							dp[i][j][k][l]=t3+square[i][j];
					}
				}
			}
		}
		System.out.println(dp[n][n][n][n]);//两人都走到最后一个方格时的值即为最大值
	}

}

传纸条

题目描述
小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标(1,1),小轩坐在矩阵的右下角,坐标(m,n)。 从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-100的自然 数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度只和最大。 现在,请你帮助小渊和小轩找到这样的两条路径。

数据规模和约定
100%的数据满足:1< =m,n< =50

输入
输入第一行有2个用空格隔开的整数m和n,表示班里有m行n列(1< =m,n< =50)。
接下来的m行是一个m*n的矩阵,矩阵中第i行j列的整数表示坐在第i行j列的学生的好心程度。每行的n个整数之间用空格隔开。

输出
输出一行,包含一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。

样例输入
3 3
0 3 9
2 8 5
5 7 0

样例输出
34

import java.util.Scanner;

//传纸条
public class Main {
     
	public static void main(String[]args) {
     
		Scanner scan=new Scanner(System.in);
		int m=scan.nextInt();
		int n=scan.nextInt();
		int num[][]=new int[m+1][n+1];
		for(int i=1;i<=m;i++) {
     
			for(int j=1;j<=n;j++) {
     
				num[i][j]=scan.nextInt();
			}
		}
		scan.close();
		
		int dp[][][][]=new int[m+1][n+1][m+1][n+1];//dp[i][j][k][l]表示一个人传到i,j,另一个人传到k,l时取得的最大值的和
		for(int i=1;i<=m;i++) {
     
			for(int j=1;j<=n;j++) {
     
				for(int k=m;k>=1;k--) {
     
					for(int l=n;l>=1;l--) {
     
						int t1=Math.max(dp[i-1][j][k-1][l], dp[i][j-1][k][l-1]);
						int t2=Math.max(dp[i-1][j][k][l-1], dp[i][j-1][k-1][l]);
						int t3=Math.max(t1, t2);
						if(i!=k && j!=l)
							dp[i][j][k][l]=t3+num[i][j]+num[k][l];
						else
							dp[i][j][k][l]=t3+num[i][j];
						
					}
				}
			}
		}
		System.out.println(dp[m][n][m][n]);
	}

}

数的划分

题目描述
一个正整数可以划分为多个正整数的和,比如n=3时:
3;1+2;1+1+1;
共有三种划分方法。
给出一个正整数,问有多少种划分方法。

数据规模和约定
n< =100

输入
一个正整数n

输出
一个正整数,表示划分方案数

样例输入
3

样例输出
3

题目解析
do[i][j]表示对数字i进行不大于j的拆分,一共有多少种拆分方法。
首先初始化1、dp[i][1]=1,把这个数划分为不大于1的方法只有一种,即拆分的每一项都为1;
2、dp[0][j]=1,把0划分的方法只有一种。
当i>=j时,dp[i][j]=dp[i][j-1]+dp[i-j][j];其中dp[i][j-1]表示划分的每一个数都小于j,dp[i-j][j]表示划分后至少有一个数字等于
当j>i时,dp[i][j]=dp[i][j];划分方法就是将数字划分为不大于本身。

import java.util.Scanner;

//数的划分
public class Main {
     
	private static int MAX = 101;
	public static void main(String[]args) {
     
		Scanner scan=new Scanner(System.in);
		int n=scan.nextInt();
		scan.close();
		
		int dp[][]=new int[MAX][MAX];
		for(int i=0;i<=n;i++) {
     
			dp[i][1]=1;
			dp[0][i]=1;
		}
		
		for(int i=1;i<=n;i++) {
     
			for(int j=1;j<=n;j++) {
     
				if(i>=j) 
					dp[i][j]=dp[i][j-1]+dp[i-j][j];
				else
					dp[i][j]=dp[i][i];
			}
		}
		System.out.println(dp[n][n]);
	}
}

传球游戏

题目描述
上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没传出去的那个同学就是败者,要给大家表演一个节目。
聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。两种传球的方法被视作不同的方 法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有3个同学1号、2号、3号,并假设小蛮为1号,球传了3次回到小蛮手里的方 式有1-> 2-> 3-> 1和1-> 3-> 2-> 1,共2种。

数据规模和约定
100%的数据满足:3< =n< =30,1< =m< =30

输入
共一行,有两个用空格隔开的整数n,m(3< =n< =30,1< =m< =30)。

输出
t共一行,有一个整数,表示符合题意的方法数。

样例输入
3 3

样例输出
2

题目解析
dp[i][j]表示传i次球,传给第j个人的方法数。
(1)dp[1][n]=1 表示传一次球,传到第n个人,有一种方法
dp[1][2]=1 表示传一次球,传到第2个人,有一种方法
dp[0][1]=1 表示自己传给自己
(2)1、如果 j= =1,即传给第一个人时,有两种情况,可能是第2个人传来的,也可能是第n个人传来的,因此dp[i][j]=dp[i-1][n]+dp[i-1][2];
2、如果j==n,即传给第n个人时,有两种情况,可能是第1个人传来的,也可能是第n-1个人传来的,因此dp[i][j]=dp[i-1][1]+dp[i-1][n-1];
3、一般情况下也有两种可能,分别是左右两边传来的,因此dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];

import java.util.Scanner;

//传球游戏
public class Main {
     
	public static void main(String[]args) {
     
		Scanner scan=new Scanner(System.in);
		int n=scan.nextInt();//n个同学
		int m=scan.nextInt();//传m次球
		scan.close();
		
		int dp[][]=new int [m+1][n+1];//dp[i][j]表示传i次球,传到第j个人的方法数
		dp[1][n]=1;//传一次球,传到第n个人,有一种方法
		dp[1][2]=1;//传一次球,传到第2个人,有一种方法
		dp[0][1]=1;//自己传给自己
		
		for(int i=1;i<=m;i++) {
     
			for(int j=1;j<=n;j++) {
     
				if(j==1) 
					dp[i][j]=dp[i-1][n]+dp[i-1][2];
				else if(j==n)
					dp[i][j]=dp[i-1][1]+dp[i-1][n-1];
				else
					dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1];
			}
		}
		System.out.println(dp[m][1]);
	}

}

入学考试

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

数据规模和约定
对于全部的数据,M < = 100。

输入
第一行有两个整数T(1 < = T < = 1000)和M(1 < = M < = 100),用一个空格隔开,T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的整 数,分别表示采摘某株草药的时间和这株草药的价值。

输出
包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

样例输入
70 3
71 100
69 1
1 2

样例输出
3

import java.util.Scanner;

//入学考试
public class Main {
     
	public static void main(String[]args) {
     
		Scanner scan=new Scanner(System.in);
		int t=scan.nextInt();//用来采草药的时间
		int m=scan.nextInt();//草药的数目
		int time[]=new int[m+1];//采到这株草药用的时间
		int value[]=new int[m+1];//这株草药的价值
		for(int i=1;i<=m;i++) {
     
			time[i]=scan.nextInt();
			value[i]=scan.nextInt();
		}
		scan.close();
		
		int dp[][]=new int [m+1][t+1];
		
		for(int i=1;i<=m;i++) {
     
			for(int j=1;j<=t;j++) {
     
				if(j>=time[i])
					dp[i][j]=Math.max(dp[i-1][j-time[i]]+value[i], dp[i-1][j]);
				else
					dp[i][j]=dp[i-1][j];
			}
		}
		System.out.println(dp[m][t]);
	}

}

开心的金明

题目描述

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的N元。于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是整数元)。他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,……,jk,则所求的总和为:

v[j1]*w[j1]+v[j2]*w[j2]+ …+v[jk]w[jk]。(其中为乘号)

请你帮助金明设计一个满足要求的购物单。

输入

输入第1行,为两个正整数,用一个空格隔开:

N m

(其中N(<30000)表示总钱数,m(<25)为希望购买物品的个数。)

从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有2个非负整数

v p

(其中v表示该物品的价格(v<=10000),p表示该物品的重要度(1~5))

输出
输出只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<100000000)。

样例输入
1000 5
800 2
400 5
300 5
400 3
200 2

样例输出
3900

import java.util.Scanner;

//开心的金明
public class Main {
     
	public static void main(String[]args) {
     
		Scanner scan=new Scanner(System.in);
		int N=scan.nextInt();//总钱数
		int m=scan.nextInt();//希望购买的物品个数
		int v[]=new int[m+1];//物品的价格
		int w[]=new int[m+1];//物品的重要度
		for(int i=1;i<=m;i++) {
     
			v[i]=scan.nextInt();
			w[i]=scan.nextInt();
		}
		scan.close();
		
		int dp[][]=new int[m+1][N+1];
		
		for(int i=1;i<=m;i++) {
     
			for(int j=1;j<=N;j++) {
     
				if(j>v[i])
					dp[i][j]=Math.max(dp[i-1][j-v[i]]+w[i]*v[i], dp[i-1][j]);
				else
					dp[i][j]=dp[i-1][j];
			}
		}
		
		System.out.println(dp[m][N]);
	}

}

你可能感兴趣的:(蓝桥杯练习)