• 6月29日训练 题解


    Super Jumping! Jumping! Jumping!

    题目链接
    给定一条长度为n的序列,其中一定存在一条元素和最大的严格上升子序列,求这条序列的元素和。

    Input

    包含多组输入数据,每组数据占一行,每行一个整数n,接着n个数a_1, a_2, ..., a_n (a_i在32位有符号整型范围内),n = 0表示输入结束 (0 <= n <= 1000)。

    Output

    一个数,严格上升子序列最大元素和。

    Sample Input

    3 1 3 2
    4 1 2 3 4
    4 3 3 2 1
    15 1 8 88 49 78 57 48 7 3 4 4 8 9 7 2
    0
    

    Sample Output

    4
    10
    3
    136
    

    思路

    若之前的位置上有小于当前位置的数, 就可以加入到序列中
    状态表示: f[i]a[i] 为结尾的最长递增序列总和
    状态属性: max
    状态计算: f[i] = max(f[i], f[j] + a[i]) 满足 a[i] > a[j]0 < j < i

    代码

    //------------------------------//
    //          Made by Aze         //
    //------------------------------//
    #include <iostream>
    #include <cstring>
    #include <string>
    #include <algorithm>
    using namespace std;
    
    
    //------- Coding Area ---------//
    const int N = 1e3 + 10;
    
    int f[N];
    int a[N];
    int n;
    int main()
    {
        while (cin >> n && n)
        {
            for(int i = 0; i < n;i ++) cin >>a[i];
            memset(f, 0, sizeof f);
            int res = 0;
            for(int i = 0; i < n;i ++)
            {
                f[i] = a[i];
                for(int j = 0; j < i; j++)
                {
                    if (a[i] > a[j])
                        f[i] = max(f[i], f[j] + a[i]);
                }
                res = max(f[i], res);
            }
            cout << res << endl;
        }
        return 0;
    }
    

    最少拦截系统

    题目链接
    某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高度.某天,雷达捕捉到敌国的导弹来袭.由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹.
    怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统.

    Input

    输入若干组数据.每组数据包括:导弹总个数(正整数),导弹依此飞来的高度(雷达给出的高度数据是不大于30000的正整数,用空格分隔)

    Output

    对应每组数据输出拦截所有导弹最少要配备多少套这种导弹拦截系统.

    Input

    8 389 207 155 300 299 170 158 65
    

    Output

    2
    

    思路

    贪心写法

    每一个导弹系统能拦截的导弹序列, 都是一个严格下降子序列。

    从头遍历, 刚开始 389 自己算一套导弹系统
    对于 207, 有两个选择:

    1. 选择加入到之前结尾大于等于207的最小的序列中
    2. 如果现有序列结尾数都小于207, 则创建新序列

    为什么是最小的大于等于207的序列, 而不是任意一个都行呢?
    假设存在两个序列结尾为 a和b
    且 a > b >= 207
    207既可以接在 a 后面, 也可以接在 b 后面
    若207接在a后面, 那么b序列只能再占一个系统
    若207接在b后面, 因为b又可以接在a后面, 那么就只需要一个系统
    相比之下, 207接在最小的大于等于207的序列后是最优解

    序列1 389 接 207 接 155
    遍历到300时, 找不到符合要求的序列, 自己新开一个
    序列2 300 接 299 接 170 接 158
    对于65, 它可以接在 序列1, 也可以接在序列2, 我们就贪心地让它接在距离它最近的一个--序列1

    最后序列为

    • 389--207--155--65
    • 300--299-170--158

    这题的贪心思想跟最长上升子序列的优化解法很像, 实现方法也相同:
    定义一个数组 g[N], 存每个序列的最小结尾
    遇到一个新导弹时, 遍历整个 g[N], 找到最小的且大于等于该导弹高度的, 更新 g[i]
    找不到时就在数组尾新加一位。

    最终答案就是整个数组的长度。

    顺便一提, 整个g数组是单调递增的, 因为每新加的一个数都是在之前找不到比自己大的才会新加。
    故遍历整个g[N] 时可以用二分来优化。

    cnt 是最终系统数量
    k 是用来找到g数组中最后一个大于等于 a[i] 的位置

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N = 2e3 +10;
    int a[N], g[N];
    int main()
    {
    	int n;
    	while(cin >> n && n)
    	{
    		int cnt = 0;
    		for(int i = 0; i < n;i ++)
    		{
    			cin >> a[i];
    			int k = 0;
    			while(k < cnt && g[k] < a[i]) k++;
    			g[k] = a[i];
    			if(k >= cnt) cnt++;
    			// 也可以 cnt = max(cnt, k + 1);
    		}
    		cout << cnt << endl;
    	}
    	return 0;
    }
    

    DP写法

    上面分析给出了一个结论:
    g数组是一个单调递增数列,也就是一个严格上升子序列

    换角度一想, 我们求的就是这个严格上升子序列的最长长度。
    诶那不跟最长上升子序列是一样的吗?

    事实确实如此, 一组数中用最少的下降序列全覆盖的个数跟这组数中的最长上升子序列长度相同。

    那就很简单了:

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N = 2e3 +10;
    int a[N], f[N];
    int main()
    {
    	int n;
    	while(cin >> n && n)
    	{
    		int res = 0;
    		for(int i = 0; i < n;i ++)
    		{
    			cin >> a[i];
    			f[i] = 1;
    			for(int j = 0; j < i; j++)
    			{// 会相等嘛?并不会, g数组相等的会在一个位置
    				if(a[i] > a[j])
    					f[i] = max(f[i], f[j] + 1);
    				res = max(f[i],res);
    			}
    		}
    		cout << res << endl;
    	}
    	return 0;
    }
    

    贪心写法是 15ms, DP写法是 30ms, 毕竟贪心本身就是求最长上升子序列的优化解法。

    Monkey and Banana

    题目链接
    一组研究人员正在设计一项实验,以测试猴子的智商。他们将香蕉挂在建筑物的屋顶,同时,提供一些砖块给这些猴子。如果猴子足够聪明,它应当能够通过合理的放置一些砖块建立一个塔,并爬上去吃他们最喜欢的香蕉。

    研究人员有n种类型的砖块,每种类型的砖块都有无限个。第i块砖块的长宽高分别用xi,yi,zi来表示。 同时,由于砖块是可以旋转的,每个砖块的3条边可以组成6种不同的长宽高。

    在构建塔时,当且仅当A砖块的长和宽都分别小于B砖块的长和宽时,A砖块才能放到B砖块的上面,因为必须留有一些空间让猴子来踩。

    你的任务是编写一个程序,计算猴子们最高可以堆出的砖块们的高度。

    Input

    输入文件包含多组测试数据。

    每个测试用例的第一行包含一个整数n,代表不同种类的砖块数目。n<=30.

    接下来n行,每行3个数,分别表示砖块的长宽高。

    当n= 0的时候,无需输出任何答案,测试结束。

    Output

    对于每组测试数据,输出最大高度。格式:Case 第几组数据: maximum height = 最大高度

    Sample Input

    1
    10 20 30  
    2  
    6 8 10  
    5 5 5  
    7  
    1 1 1  
    2 2 2  
    3 3 3  
    4 4 4  
    5 5 5  
    6 6 6  
    7 7 7  
    5  
    31 41 59  
    26 53 58  
    97 93 23  
    84 62 64  
    33 83 27  
    0 
    

    Sample Output

    Case 1: maximum height = 40
    Case 2: maximum height = 21  
    Case 3: maximum height = 28  
    Case 4: maximum height = 342
    

    思路

    对于长和宽需要保证上面砖块小于下面砖块, 可以转化为最大上升子序列和。
    有6种情况就把每种情况当成一个独立的砖。
    注意cmp函数的写法, 我换个写法就WA和TLE, 这用的是一个优化的cmp, 效率会高些

    我写的是正序从小到大, 其实逆序得出的结果一样。

    #include <cstring>
    #include <algorithm>
    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    //------- Coding Area ---------//
    const int N = 10000;
    // int f[N][N];
    int f[N];
    int n;
    
    struct Point
    {
        int first, second, h;
    };
    Point g[N];
    struct cmpFunctor
    {
    	inline bool operator() (const Point& t1, const Point&t2)
    	{
    		if(t1.first == t2.first)
    		{
    			return t1.second < t2.second;
    		}
    		else return t1.first < t2.first;
    	}
    };
    
    
    int main()
    {
        int Case = 1;
        while (scanf("%d",&n) != EOF)
        {
        	if(!n) break;
            int res = 0, k = 0;
            for (int i = 0; i < n; i++)
            {
                int a, b, c;
                scanf("%d%d%d", &a, &b, &c);
                g[k++] = { a,b,c };
                g[k++] = { a, c, b };
                g[k++] = { b, a, c };
                g[k++] = { b, c, a };
                g[k++] = { c, a, b };
                g[k++] = { c, b, a };
            }
            
            sort(g, g + k,cmpFunctor());
            
            for (int i = 0; i < k; i++)
            {
                f[i] = g[i].h;
                for (int j = 0; j < i; j++)
                {
                    if (g[i].first > g[j].first && g[i].second > g[j].second)
                        f[i] = max(f[i], f[j] + g[i].h);
                }
                res = max(f[i], res);
            }
            printf("Case %d: maximum height = %d\n", Case++, res);
        }
        return 0;
    }
    
  • 相关阅读:
    剑指Offer(Java版)第五十题:牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志, 写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看, 但却读不懂它的意思。例如,“student. a am I”
    剑指Offer(Java版)第四十九题:汇编语言中有一种移位指令叫做循环左移(ROL), 现在有个简单的任务,就是用字符串模拟这个指令的运算结果。 对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。 例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果, 即“XYZdefabc”。是不是很简单?OK,搞定它!
    【转载】Java 内存分配全面浅析
    【记】Linux下安装JDK1.7
    【ZooKeeper】典型应用场景概览
    正则表达式工具RegexBuddy
    【基础】RandomAccess
    【JNDI】Java Naming and Directory Interface
    【AOP】Cglib动态代理实现方式
    【事务】分布式事物原理
  • 原文地址:https://www.cnblogs.com/edwinaze/p/16424092.html
Copyright © 2020-2023  润新知