第一章动态规划(二)

1. 最长上升子序列模型

例题:怪盗基德的滑翔翼
原题链接

总时间限制:
1000ms
内存限制:
65536kB

描述

怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。

有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。不得已,怪盗基德只能操作受损的滑翔翼逃脱。
在这里插入图片描述

假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。初始时,怪盗基德可以在任何一幢建筑的顶端。他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?

输入
输入数据第一行是一个整数K(K < 100),代表有K组测试数据。
每组测试数据包含两行:第一行是一个整数N(N < 100),代表有N幢建筑。第二行包含N个不同的整数,每一个对应一幢建筑的高度h(0 < h < 10000),按照建筑的排列顺序给出。
输出
对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。
样例输入
3
8
300 207 155 299 298 170 158 65
8
65 158 170 298 299 155 207 300
10
2 1 3 4 5 6 7 8 9 10

样例输出
6
6
9
——————————————————————————————————————————————————
1.随意选个楼顶作为起点。
2.随意选个方向,选定方向就固定了。
这两点确定了之后,假定以 a[i] 为起点,则最长距离就是以 a[i] 为结尾的最长上升子序列。在两个方向上(左到右,右到左)各求一次最长上升子序列即可。

import java.util.Scanner;

public class Main{
	static int N = 110;
	static int n;
	static int K;
	static int[] a = new int[N];
	static int[] dp = new int[N];
	
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        K = sc.nextInt();
        while(K-- > 0) {
        	n = sc.nextInt();
        	for(int i = 1;i <= n;i++) a[i] = sc.nextInt();
        	
        	//从左到右求一遍
        	int maxLen = 0;
        	for(int i = 1;i <= n;i++) {
        		dp[i] = 1;//初始化
        		for(int j = 1;j < i;j++) {
        			if(a[j] < a[i]) {
        				dp[i] = Math.max(dp[i], dp[j] + 1);
        			}
        		}
        		maxLen = Math.max(maxLen, dp[i]);
        	}
        	//从右往左求一遍
        	for(int i = n;i >= 1;i--) {
        		dp[i] = 1;
        		for(int j = n;j > i;j--) {
        			if(a[j] < a[i]) {
        				dp[i] = Math.max(dp[i], dp[j] + 1);
        			}
        		}
        		maxLen = Math.max(maxLen, dp[i]);
        	}
        	System.out.println(maxLen);
        }
        sc.close();
	}
}

例题:登山
原题链接

总时间限制:
5000ms
内存限制:
131072kB

描述
五一到了,PKU-ACM队组织大家去登山观光,队员们发现山上一个有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
输入
Line 1: N (2 <= N <= 1000) 景点数
Line 2: N个整数,每个景点的海拔
输出
最多能浏览的景点数
样例输入
8
186 186 150 200 160 130 197 220
样例输出
4
————————————————————————————————————————————————————
条件:

  1. 编号递增----->必须是个子序列
  2. 不连续浏览海拔相同的两个景点
  3. 一旦开始下山,就不再向上走------->路线是先严格上升再严格下降
    在这里插入图片描述
import java.util.Scanner;

public class Main{
	static int N = 1010;
	static int n;
	static int[] a = new int[N];
	static int[] f = new int[N];
	static int[] g = new int[N];
	
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        for(int i = 1;i <= n;i++) a[i] = sc.nextInt();
        sc.close();
        
        //求f[]
        for(int i = 1;i <= n;i++) {
        	f[i] = 1;//初始化
        	for(int j = 1;j < i;j++) {
        		if(a[j] < a[i]) f[i] = Math.max(f[i], f[j] + 1);
        	}
        }
        //求g[]
        for(int i = n;i >= 1;i--) {
        	g[i] = 1;//初始化
        	for(int j = n;j > i;j--) {
        		if(a[j] < a[i]) g[i] = Math.max(g[i], g[j] + 1);
        	}
        }
        //枚举所有以a[i]为顶点的最大结果
        int res = 0;
        for(int i = 1;i <= n;i++) {
        	res = Math.max(res, f[i] + g[i] - 1);
        }
        System.out.println(res);
	}
}

例题:合唱队形
原题链接
在这里插入图片描述
————————————————————————————————————————————————————
可以知道合唱队列和上一题的登山路线形状是一样的,都是先上升后下降,本题问最少挑出几名同学,那不就是在问最多剩下多少同学满足队形,所以和上一题本质一样的。

import java.util.Scanner;

public class Main{
	static int N = 110;
	static int n;
	static int[] a = new int[N];
	static int[] f = new int[N];
	static int[] g = new int[N];
	
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        for(int i = 1;i <= n;i++) a[i] = sc.nextInt();
        sc.close();
        
        //求f[]
        for(int i = 1;i <= n;i++) {
        	f[i] = 1;//初始化
        	for(int j = 1;j < i;j++) {
        		if(a[j] < a[i]) f[i] = Math.max(f[i], f[j] + 1);
        	}
        }
        //求g[]
        for(int i = n;i >= 1;i--) {
        	g[i] = 1;//初始化
        	for(int j = n;j > i;j--) {
        		if(a[j] < a[i]) g[i] = Math.max(g[i], g[j] + 1);
        	}
        }
        //枚举所有以a[i]为顶点的最大结果
        int res = 0;
        for(int i = 1;i <= n;i++) {
        	res = Math.max(res, f[i] + g[i] - 1);
        }
        System.out.println(n - res);//只是输出不一样
	}
}

例题:友好城市
原题链接
【题目描述】
Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。

每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。
【输入】
第1行,一个整数N(1≤N≤5000),表示城市数。
第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。(0≤xi≤10000)
【输出】
仅一行,输出一个整数,表示政府所能批准的最多申请数。
【输入样例】
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
【输出样例】
4
——————————————————————————————————————————————————
样例模拟:
在这里插入图片描述
每一种合法的建桥方式都对应一个上升子序列。最多能建的桥数就对应最长上升子序列长度。
将友好城市对,按照南岸坐标排序,寻找最长上升子序列。

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

public class Main{
	static int N = 5010;
	static int n;
	static Pair[] q = new Pair[N];
	static int[] f = new int[N];
	
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        for(int i = 0;i < n;i++) {
        	int x = sc.nextInt();
        	int y = sc.nextInt();
        	q[i] = new Pair(x, y);
        }
        sc.close();
        
        Arrays.sort(q, 0, n, new Comparator<Pair>() {
        	@Override
        	public int compare(Pair o1, Pair o2) {
        		return o1.x - o2.x;
        	}
		});
        int res = 0;
        for(int i = 0;i < n;i++) {
        	f[i] = 1;
        	for(int j = 0;j < i;j++) {
        		if(q[i].y > q[j].y) {
        			f[i] = Math.max(f[i], f[j] + 1);
        		}
        	}
        	res = Math.max(res, f[i]);
        }
        System.out.println(res);
	}
}
class Pair{
	int x;
	int y;
	public Pair(int x, int y) {
		this.x = x;
		this.y = y;
	}
}

例题:最大上升子序列和
原题链接
在这里插入图片描述
在这里插入图片描述

import java.util.Scanner;

public class Main{
	static int N = 1010;
	static int n;
	static int[] a = new int[N];
	static int[] f = new int[N];
	
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        for(int i = 1;i <= n;i++) {
        	a[i] = sc.nextInt();
        }
        sc.close();
        
        for(int i = 1;i <= n;i++) {
        	f[i] = a[i];//初始化
        	for(int j = 1;j < i;j++) {
        		if(a[j] < a[i]) {
        			f[i] = Math.max(f[i], f[j] + a[i]);
        		}
        	}
        }
        //枚举一遍f[]
        int res = 0;
        for(int i = 1;i <= n;i++) res = Math.max(res, f[i]);
        System.out.println(res);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值