题目描述
小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共m盆。通过调查顾客的喜好,小明列出了顾客最喜欢的n种花,从1到n标号。为了在门口展出更多种花,规定第i种花不能超过ai盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。
试编程计算,一共有多少种不同的摆花方案。
输入格式
第一行包含两个正整数n和m,中间用一个空格隔开。
第二行有n个整数,每两个整数之间用一个空格隔开,依次表示a1 ,a2 ,…,an 。
输出格式
一个整数,表示有多少种方案。注意:因为方案数可能很多,请输出方案数对1000007取模的结果。
输入输出样例
输入 #1复制
2 4
3 2
输出 #1复制
2
说明/提示
【数据范围】
对于20%数据,有0<n≤8,0<m≤8,0≤ai ≤8;
对于50%数据,有0<n≤20,0<m≤20,0≤ai ≤20;
对于100%数据,有0<n≤100,0<m≤100,0≤ai ≤100。
思路:在n种物品中,每种物品选择若干,一共选择m件的方案数有多少。多重背包?
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); //n种花
int m = sc.nextInt();//一共摆m盆
int[] arr= new int[110];
for(int i=1;i<=n;i++){
arr[i] = sc.nextInt();
}
int[][] dp = new int[110][110]; //dp[i][j]表示前i种摆j盆的方案数
/*for(int i=0;i<=n;i++){
dp[i][0] = 1;
}*/
dp[0][0]=1;
for(int i=1;i<=n;i++){ //前i个
for(int j=0;j<=m;j++){ //摆j盆
for(int k=0;k<=arr[i];k++){
if(j>=k) dp[i][j] =(dp[i][j]+dp[i-1][j-k])%1000007;
}
}
}
System.out.print(dp[n][m]);
}
}
还可以再空间上进行优化:
import java.util.Arrays;
import java.util.Scanner;
public class Main{
//n种花 摆m盆一共有多少种方案数
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); //n种花
int m = sc.nextInt();//一共摆m盆
int[] dp = new int[110]; //dp[i]表示摆i盆的方案数
int[] arr= new int[110];
for(int i=0;i<n;i++){
arr[i] = sc.nextInt();
}
dp[0] = 1;
for(int i=0;i<n;i++){
for(int j=m;j>=0;j--){
for(int k = 1;k<=arr[i];k++){
if(j>=k){
dp[j] =(dp[j]+dp[j-k])%1000007;
}
}
}
}
System.out.print(dp[m]);
}
}
导弹拦截
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是≤50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
1行,若干个整数(个数≤100000)
输出格式
2行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入输出样例
输入 #1复制
389 207 155 300 299 170 158 65
输出 #1复制
6
2
思路:很显然,此题是一道最长不下降子序列的模板题。题目给的数据范围是10的5次方,所以我们应该 采用优化过的算法。而第二问,至少需要多少套设备拦截的数量,就是求最长上升子序列的长度。最后打印出两个长度,便可以愉快的ac了。
import java.util.Scanner;
public class Main{
//找最长不上升子序列 最长上升子序列
// 1: 判断是否不大于最后一个数 是 则直接加在最后 不是 则二分查找第一个小于他的数并且替换他
// 2:判断是否大于最后一个数 是 则直接加在最后 不是 则二分查找第一个大于等于他的数并且替换他
static int x0,y0,x1,y1;
public static void main(String[] args) {
int count1 = 0;
int count2 = 0;
int[] len1 = new int[100010];
int[] len2 = new int[100010];
Scanner sc = new Scanner(System.in);
String[] str = sc.nextLine().split(" ");
int[] arr = new int[str.length];
for(int i=0;i<str.length;i++){
arr[i] = Integer.parseInt(str[i]);
}
len1[count1++] = arr[0];
len2[count2++] = arr[0];
for(int i=1;i<arr.length;i++){
if(arr[i]<=len1[count1-1]){
len1[count1++] = arr[i];
}else{
binIndex1(arr[i],len1,count1);
}
if(arr[i]>len2[count2-1]){
len2[count2++] = arr[i];
}else{
binIndex2(arr[i],len2,count2);
}
}
System.out.print(count1+"
"+count2);
}
public static void binIndex1(int x,int[] arr,int count){ //在长度是count的arr数组中找第一个小于他的数并且替换他
int left = 0;
int right= count;
while(left<right){
int mid = (left+right)>>1;
if(arr[mid]<x){
right = mid;
}else{
left = mid+1;
}
}
arr[right] = x;
}
public static void binIndex2(int x,int[] arr,int count){ 在长度是count的arr数组中找第一个大于等于他的数并且替换他
int left = 0;
int right = count;
while(left<right){
int mid = (left+right)>>1;
if(arr[mid]>=x){
right = mid;
}else {
left = mid+1;
}
}
arr[right] = x;
}
}
木棍加工
题目描述
一堆木头棍子共有n根,每根棍子的长度和宽度都是已知的。棍子可以被一台机器一个接一个地加工。机器处理一根棍子之前需要准备时间。准备时间是这样定义的:
第一根棍子的准备时间为1分钟;
如果刚处理完长度为L,宽度为W的棍子,那么如果下一个棍子长度为Li,宽度为Wi,并且满足L>=Li,W>=Wi,这个棍子就不需要准备时间,否则需要1分钟的准备时间;
计算处理完n根棍子所需要的最短准备时间。比如,你有5根棍子,长度和宽度分别为(4, 9),(5, 2),(2, 1),(3, 5),(1, 4),最短准备时间为2(按(4, 9)、(3, 5)、(1, 4)、(5, 2)、(2, 1)的次序进行加工)。
输入格式
第一行是一个整数n(n<=5000),第2行是2n个整数,分别是L1,W1,L2,w2,…,Ln,Wn。L和W的值均不超过10000,相邻两数之间用空格分开。
输出格式
仅一行,一个整数,所需要的最短准备时间。
输入输出样例
输入 #1复制
5
4 9 5 2 2 1 3 5 1 4
输出 #1复制
2
思路:首先这道题需要木棍的两个属性(暂且称为属性)都是不下降的一个序列,换句话说,就是两个属性都具有一定的单调性
那么这样一个问题可以理解成一个矩形的覆盖问题,木棍的长与宽就是矩形的长与宽,如果一个矩形能够完全覆盖另一矩形(即一个木棍的长和宽都大于等于另一木棍)则处理下一木棍就不需要准备时间,这样就实现了本题的目标,即让加工木棍的准备时间最短。
那么,怎样同时保证木棍的两个属性都具有单调性呢? 显然要先对一个属性排序sort大法好
接下来另一组属性的单调递减只需要求出不下降子序列的个数即可,因为从一个子序列转换到另一子序列只需一分钟的准备时间,所以不下降子序列的个数就是总共的准备时间。
从dilworth定理中,我们可知,下降子序列的最小划分等于最长不下降子序列的长度
于是题目转换为一道基本的dp求最长不下降子序列的题目
接下来的思路就类似于导弹拦截的思路了
我们用f[n]来表示长度为n的最长不下降子序列的结束点,接下来对于每一个木棍的长度a[i].l,如果它小于等于当前最长序列的结束点,则可将它添加到当前序列的末尾,并将序列长度增加1;
如果不是这样,我们可以通过二分查找找到最接近它但是大于等于它的结束点,并更新这个点。(可以思考一下为什么能够利用二分查找进行查找)
import java.util.Arrays;
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
Node[] arr = new Node[n];
for(int i=0;i<n;i++){
int t1 = sc.nextInt();
int t2 = sc.nextInt();
arr[i] = new Node(t1,t2);
}
Arrays.sort(arr);
int count = 0;
Node[] nodes = new Node[5050]; //最长上升子序列的长度
nodes[count++] = arr[0];
for(int i=1;i<n;i++){
if(nodes[count-1].y<arr[i].y){
nodes[count++] = arr[i];
}else{
bin(nodes,arr[i],count);
}
}
System.out.print(count);
}
public static void bin(Node[] nodes,Node node,int count){ //替换第一个大于等于它的元素
int left = 0;
int right = count;
while(left<right){
int mid = (left+right)>>1;
if(node.y<=nodes[mid].y){
right = mid;
}else {
left = mid+1;
}
}
nodes[right] = node;
}
}
class Node implements Comparable<Node>{
int x,y;
public Node(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public int compareTo(Node o) {
if(x!=o.x){
return o.x - x;
}else {
return o.y - y;
}
}
}
合唱队形
题目描述
N位同学站成一排,音乐老师要请其中的(N−K)位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,…,K,他们的身高分别为T1,T2 ,…,TK , 则他们的身高满足T1 <…Ti+1 >…>TK (1≤i≤K)。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
共二行。
第一行是一个整数N(2≤N≤100),表示同学的总数。
第二行有n个整数,用空格分隔,第i个整数Ti (130≤Ti ≤230)是第ii位同学的身高(厘米)。
输出格式
一个整数,最少需要几位同学出列。
输入输出样例
输入 #1复制
8
186 186 150 200 160 130 197 220
输出 #1复制
4
说明/提示
对于50%的数据,保证有n≤20;
对于全部的数据,保证有n≤100。
思路:最长上升子序列。n的数据范围比较小,我们可以采用n方的做法。dp1[i]储存以i为结尾的最长上升子序列的长度,dp2[i]储存以i为开头的最长下降子序列的长度。
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] arr = new int[n];
for(int i=0;i<n;i++){
arr[i] = sc.nextInt();
}
int[] dp1 = new int[n]; //以i结尾的最长上升子序列的长度
int[] dp2 = new int[n];//以i开头的最长下降子序列的长度
for(int i=0;i<arr.length;i++){
dp1[i] = 1;
for(int j=0;j<i;j++){
if(arr[j]<arr[i]){
dp1[i] = Math.max(dp1[j]+1,dp1[i]);
}
}
}
for(int i=arr.length-1;i>=0;i--){
dp2[i] = 1;
for(int j=i+1;j<arr.length;j++){
if(arr[j]<arr[i]){
dp2[i] = Math.max(dp2[j]+1,dp2[i]);
}
}
}
int ans = 0;
for(int i=0;i<arr.length;i++){
ans = Math.max(ans,dp1[i]+dp2[i]);
}
System.out.print(n-ans+1);
}
}
【模板】最长公共子序列
题目描述
给出 1,2,…,n 的两个排列 P1和 P2 ,求它们的最长公共子序列。
输入格式
第一行是一个数 n。
接下来两行,每行为 n 个数,为自然数 1,2,…,n 的一个排列。
输出格式
一个数,即最长公共子序列的长度。
输入输出样例
输入 #1复制
5
3 2 1 4 5
1 2 3 4 5
输出 #1复制
3
思路:最长公共子序列的模板。
import java.util.Scanner;
public class Main{
//找p1 p2的最长公共子序列 很显然,当两个串中有一个为空的时候,那么公共子序列的长度为0
//当p1[i] = p2[j]的时候 两个串的最长公共子序列就是dp[i-1][j-1]+1
//当p1[i]!= p2[j]的时候 两个串的最长公共子序列就是max(dp[i-1][j],dp[i][j-1])
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] p1 = new int[n+1];
int[] p2 = new int[n+1];
int[][] dp = new int[n+1][n+1];
for(int i=1;i<=n;i++){
p1[i] = sc.nextInt();
}
for(int i=1;i<=n;i++){
p2[i] = sc.nextInt();
}
for(int i=1;i<=n;i++){
for(int j =1;j<=n;j++){
if(p1[i]==p2[j]){
dp[i][j] = dp[i-1][j-1]+1;
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
System.out.print(dp[n][n]);
}
}
编辑距离
题目描述
设A和B是两个字符串。我们要用最少的字符操作次数,将字符串A转换为字符串B。这里所说的字符操作共有三种:
1、删除一个字符;
2、插入一个字符;
3、将一个字符改为另一个字符;
!皆为小写字母!
输入格式
第一行为字符串A;第二行为字符串B;字符串A和B的长度均小于2000。
输出格式
只有一个正整数,为最少字符操作次数。
输入输出样例
输入 #1复制
sfdqxbw
gfdgw
输出 #1复制
4
思路:编辑距离模板题,我们用dp[i][j]表示s1前i位和s2前j位匹配所需要的操作数。
注意防止数组下标越界。
import java.util.Scanner;
public class Main{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s1 = sc.next();
String s2 = sc.next();
char[] ch1 = s1.toCharArray();
char[] ch2 = s2.toCharArray();
int[][] dp = new int[2005][2005]; //dp[i][j]表示s1前i位与s2前j为匹配需要修改多少次
for(int i=1;i<= ch1.length;i++){
dp[i][0] = i;
}
for(int j=1;j<= ch2.length;j++){
dp[0][j] = j;
}
for(int i=1;i<=ch1.length;i++){
for(int j=1;j<=ch2.length;j++){
if(ch1[i-1] == ch2[j-1]){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = Math.min(dp[i-1][j-1],dp[i-1][j]);
dp[i][j] = Math.min(dp[i][j],dp[i][j-1]);
dp[i][j] ++;
}
}
}
System.out.print(dp[ch1.length][ch2.length]);
}
}