(算法提高课)动态规划-最长上升子序列模型1

1017. 怪盗基德的滑翔翼

怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。
而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。
有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。
不得已,怪盗基德只能操作受损的滑翔翼逃脱。
假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。
初始时,怪盗基德可以在任何一幢建筑的顶端。
他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。
因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。
他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。
请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?
输入格式
输入数据第一行是一个整数K,代表有K组测试数据。
每组测试数据包含两行:第一行是一个整数N,代表有N幢建筑。第二行包含N个不同的整数,每一个对应一幢建筑的高度h,按照建筑的排列顺序给出。
输出格式
对于每一组测试数据,输出一行,包含一个整数,代表怪盗基德最多可以经过的建筑数量。
数据范围
1≤K≤100,
1≤N≤100,
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

最长不下降序列和最长不上升子序列(算法思想相同,本题同时考察两个方向)

//参考最长不下降子序列代码模型
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N];
int dp1[N], dp2[N];  //用来记录以序号I的结尾的序列中,最长不上升序列有多长

int main()
{
    int K;
    cin>>K;
    for(int k = 0;k < K;k ++){
        int n;
        scanf("%d",&n);
        for(int i = 1;i <= n;i ++){
            scanf("%d",&a[i]);
        }
        
        int ans = -1;
        
        for(int i = 1;i <= n;i ++){
            dp1[i] = 1; //进行初始化,默认长度为1
            dp2[i] = 1;
            //依次递减(正向下降)
            for(int j = 1;j < i;j ++){
                if(a[j] > a[i] && dp1[j] + 1 > dp1[i]){
                    //如果存在比当前楼栋高,且加上a[i]后序列长度大于a[i]此时的值
                    dp1[i] = dp1[j] + 1;
                }else if(a[j] < a[i] && dp2[j] + 1 > dp2[i]) {
                    dp2[i] = dp2[j] + 1;
                }
            }
            ans = max(ans, dp1[i]);
            ans = max(ans, dp2[i]);
            
        }
        cout<<ans<<endl;
    }

    return 0;
}

1014. 登山

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

自己写的版本,通过但运行时间很长(因为寻找以当前字母为开头的递减序列的时候,外层循环中内嵌了一个双层循环,变成了三重循环)

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N];
int dp[4][N];
int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1;i <= n;i ++){
        scanf("%d", &a[i]);
        dp[1][i] = dp[2][i] = 1;
    }
    n = unique(a + 1, a + n + 1) - (a + 1);  //进行相邻元素去重

    int ans = -1;

    for(int i = 1;i <= n;i ++){
        //呈山峰状,中间高两边低,往前找小的,往后也找小的
        //找寻以a[i]结尾的不递减序列
        for(int j = 1;j < i;j ++){
            if(a[j] < a[i] && dp[1][j] + 1 > dp[1][i]){
                dp[1][i] = dp[1][j] + 1;
            }
        }
        //寻找以a[i]开头的不上升序列
        for(int j = n - 1;j >= i;j --){
            for(int k = n;k > j;k --){
                if(a[k] < a[j] && dp[2][k] + 1 > dp[2][j]){
                    dp[2][j] = dp[2][k] + 1;
                }
            }
        }
        dp[3][i] = dp[1][i] + dp[2][i] - 1;
        ans = max(ans, dp[3][i]);
    }
    cout<<ans;
    return 0;
}

运行结果如下:
运行结果图片
简化版本和自己写的版本思路是一模一样的,不一样的就是走三重循环那个部分抽出来走了第二次循环(想到过但是没实施)
新代码对时间的优化程度很高

//简化版本的思路是一样的,只是没有一次完成,也就没有了走三重循环的必要,直接拆成两次独立的双重循环
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N];
int f[N], g[N];

void dp(int f[],int n)
{
    for(int i = 1;i <= n;i ++){
        for(int j = 1;j < i;j ++){
            if(a[j] < a[i])
                f[i] = max(f[i], f[j] + 1);
        }
    }

}
int main()
{
    int n; 
    scanf("%d", &n);
    for(int i = 1;i <= n;i ++){
        scanf("%d", &a[i]);
        f[i] = g[i] = 1;
    }
    n = unique(a + 1, a + n + 1) - (a+1);
    dp(f, n);   //从低到高找一次
    reverse(a + 1, a + n + 1);
    dp(g, n);  //再反向从a[n]到a[1]从低到高找一次,其实和原本的思路一样,但这里简化了过程

    int ans = 0;
    for(int i = 1;i <= n;i ++){
        ans = max(ans, f[i] + g[n + 1 - i] - 1);
    }
    cout<<ans;
    return 0;
}

482. 合唱队形(可跳过)

N位同学站成一排,音乐老师要请其中的 (N−K)位同学出列,使得剩下的 K位同学排成合唱队形。     
合唱队形是指这样的一种队形:设 K位同学从左到右依次编号为 1,2…,K,他们的身高分别为 T1,T2,…,TK,则他们的身高满足
T 1 < … < T i > T i + 1 > … > T K   ( 1 ≤ i ≤ K ) T_1<…<T_i>T_i+1>…>T_K ~(1≤i≤K) T1<<Ti>Ti+1>>TK (1iK)  
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
输入的第一行是一个整数 N,表示同学的总数。
第二行有 N个整数,用空格分隔,第 i i i个整数 T i T_i Ti是第 i i i 位同学的身高(厘米)。
输出格式
输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。
数据范围
2≤N≤100,
130≤Ti≤230
输入样例:
8
186 186 150 200 160 130 197 220
输出样例:
4

跟上题基本上一模一样,减去上题的答案即为所求

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N];
int f[N], g[N];

void dp(int f[],int n)
{
    for(int i = 1;i <= n;i ++){
        for(int j = 1;j < i;j ++){
            if(a[j] < a[i])
                f[i] = max(f[i], f[j] + 1);
        }
    }

}
int main()
{
    int m; 
    scanf("%d", &m);
    for(int i = 1;i <= m;i ++){
        scanf("%d", &a[i]);
        f[i] = g[i] = 1;
    }
    int n = unique(a + 1, a + m + 1) - (a+1);
    dp(f, n);   
    reverse(a + 1, a + n + 1);
    dp(g, n); 

    int ans = 0;
    for(int i = 1;i <= n;i ++){
        ans = max(ans, f[i] + g[n + 1 - i] - 1);
    }
    cout<<m - ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值