动态规划__最长上升子序列

目录

一.最长上升子序列

最长上升子序列模板 O(n ^ 2)

最长上升子序列二分优化 O(nlongn)

1017. 怪盗基德的滑翔翼

1014. 登山

1012. 友好城市

1016. 最大上升子序列和

1010. 拦截导弹

187. 导弹防御系统

二.最长公共上升子序列

最长公共子序列

最长公共上升子序列


一.最长上升子序列

最长上升子序列模板 O(n ^ 2)

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
输入格式
第一行包含整数 N。
第二行包含 N个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。

f[i] 初始化为1,即集合中只有a[i]

f[i] = max(f[i], f[j] + 1) (a[j] < a[i])

则最大长度为以每一个a[i]结尾的最大值f[i]的最大值

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int f[N], a[N];

int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    
    for (int i = 1; i <= n; i ++)
    {
        f[i] = 1;
        for (int j = 1; j < i; j ++)
            if (a[j] < a[i]) f[i] = max(f[i], f[j] + 1);
    }
    
    int res = 0;
    for (int i = 1; i <= n; i ++) res = max(res, f[i]);
    
    cout << res;
    
    return 0;
}

最长上升子序列二分优化 O(nlongn)

遍历整个a数组,用q数组来存储所有不同长度的上升子序列的最小值

用二分来求q数组中第一个小于等于a[i]的数,并且将len和q数组更新

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

int a[N], q[N];

int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i ++) cin >> a[i];
    
    q[0] = -2e9;
    int len = 0;
    for (int i = 0; i < n; i ++)
    {
        int l = 0, r = len;
        while (l < r)
        {
            int mid = l + r + 1 >> 1;
            if (q[mid] < a[i]) l = mid;
            else r = mid - 1;
        }
        len = max(len, r + 1);
        q[r + 1] = a[i];
    }
    
    cout << len;
    
    return 0;
}

1017. 怪盗基德的滑翔翼

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

思路:只能向下滑行,问最多可以经过多少幢建筑。即求最长下降子序列。

又因为他可以选择一个方向逃跑,则还需倒序求最长下降子序列,然后取最大值。

代码如下

#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int a[N], f[N];

int main()
{
    int t;
    cin >> t;
    while(t --)
    {
        int n;
        cin >> n;
        for (int i = 1; i <= n; i ++) cin >> a[i];
        
        int res = 0;
        for (int i = 1; i <= n; i ++)
        {
            f[i] = 1;
            for (int j = 1; j < i; j ++)
                if (a[j] > a[i]) f[i] = max(f[i], f[j] + 1);
            res = max(f[i], res);
        }
        
        for (int i = n; i > 0; i --)
        {
            f[i] = 1;
            for (int j = n; j > i; j --)
                if (a[j] > a[i]) f[i] = max(f[i], f[j] + 1);
            res = max(f[i], res);
        }
            
        cout << res << endl;
    }
}

1014. 登山

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

思路:开始下山就不再向上走了,即求先上升再下降的最长子序列(f[i]+g[i]-1的最大值)

f[i] 表示以a[i]结尾的最长上升子序列

g[i] 表示以a[i]开始的最长下降子序列,即为倒序的以a[i]结尾的最长上升子序列

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int a[N], f[N], g[N];

int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    
    for (int i = 1; i <= n; i ++)
    {
        f[i] = 1;
        for (int j = 1; j < i; j ++)
            if (a[j] < a[i]) f[i] = max(f[i], f[j] + 1);
    }
    for (int i = n; i > 0; i --)
    {
        g[i] = 1;
        for (int j = n; j > i; j --)
            if (a[j] < a[i]) g[i] = max(g[i], g[j] + 1);
    }
    
    int res = 0;
    for (int i = 1; i <= n; i ++)
        res = max(res, f[i] + g[i] - 1);
        
    cout << res;
    
    return 0;
}

1012. 友好城市

Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。

北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。

每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。

编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。

输入格式
第1行,一个整数N,表示城市数。

第2行到第n+1行,每行两个整数,中间用1个空格隔开,分别表示南岸和北岸的一对友好城市的坐标。

输出格式
仅一行,输出一个整数,表示政府所能批准的最多申请数。

数据范围
1≤N≤5000
0≤xi≤10000
输入样例:
7
22 4
2 6
10 3
15 12
9 8
17 17
4 2
输出样例:
4

思路: 由于要避免任意两条航线相交,所以连接的城市的编号应该同增或者同减,用pair<int,int>来存储一对可以连接的城市,再将一组城市编号从小到大排序,然后求这个可以对应的最长上升子序列。

#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;

const int N = 5010;

int a[N];
int f[N];
PII city[N];

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++)
        scanf("%d%d", &city[i].first, &city[i].second);
    sort(city, city + n + 1);
    
    int res = 0;
    for (int i = 1; i <= n; i ++)
    {
        f[i] = 1;
        for (int j = 1; j < i; j ++)
            if (city[i].second > city[j].second) f[i] = max(f[i], f[j] + 1);
        res = max(res, f[i]);
    }
    printf("%d", res);
    
    return 0;
}

1016. 最大上升子序列和

1016. 最大上升子序列和
一个数的序列 bi,当 b1<b2<…<bS 的时候,我们称这个序列是上升的。

对于给定的一个序列(a1,a2,…,aN),我们可以得到一些上升的子序列(ai1,ai2,…,aiK),这里1≤i1<i2<…<iK≤N。

比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。

这些子序列中和最大为18,为子序列(1,3,5,9)的和。

你的任务,就是对于给定的序列,求出最大上升子序列和。

注意,最长的上升子序列的和不一定是最大的,比如序列(100,1,2,3)的最大上升子序列和为100,而最长上升子序列为(1,2,3)。
#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n;
int a[N];
int f[N];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    
    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] = max(f[i], f[j] + a[i]);
    }
    
    int res = 0;
    for (int i = 1; i <= n; i ++) res = max(res, f[i]);
    
    cout << res;
    
    return 0;
}

1010. 拦截导弹

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。

由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式
共一行,输入导弹依次飞来的高度。

输出格式
第一行包含一个整数,表示最多能拦截的导弹数。

第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。

数据范围
雷达给出的高度数据是不大于 30000 的正整数,导弹数不超过 1000。

输入样例:
389 207 155 300 299 170 158 65
输出样例:
6
2

 思路:题目的第一问即求最长不下降子序列;

            第二问求最少需要的系统数,可以用贪心的思想来求。对于第i号导弹,要么选择末尾导弹高度最小的拦截系统,要么新建一个拦截系统,用一个数字表示每套拦截系统此时所拦截的最后一个导弹高度,来表示该系统。这样就得到了一个数组,而数组最终长度就是所需最少拦截系统数。

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n;
int a[N], g[N], f[N];

int main()
{
    while (cin >> a[n]) n ++;
    
    int len = 0, cnt = 0;
    for (int i = 0; i < n; i ++)
    {
        f[i] = 1;
        for (int j = 0; j < i; j ++)
            if (a[j] >= a[i]) f[i] = max(f[i], f[j] + 1);
        len = max(len, f[i]); 
        
        int k = 0;
        while (k < cnt && g[k] < a[i]) k ++;
        g[k] = a[i];
        if (k == cnt) cnt ++;
    }
    
    cout << len << endl << cnt;
    
    return 0;
}

187. 导弹防御系统

为了对抗附近恶意国家的威胁,R 国更新了他们的导弹防御系统。

一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。

例如,一套系统先后拦截了高度为 3 和高度为 4 的两发导弹,那么接下来该系统就只能拦截高度大于 4 的导弹。

给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。

输入格式
输入包含多组测试用例。

对于每个测试用例,第一行包含整数 n,表示来袭导弹数量。

第二行包含 n 个不同的整数,表示每个导弹的高度。

当输入测试用例 n=0 时,表示输入终止,且该用例无需处理。

输出格式
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。

数据范围
1≤n≤50
5
3 5 2 4 1

输出样例:
2
样例解释
对于给出样例,最少需要两套防御系统。

一套击落高度为 3,4 的导弹,另一套击落高度为 5,2,1 的导弹。

 思路:DFS遍历所有可能情况,求出需要系统最小值

           搜索顺序分为两个阶段:从前往后枚举每颗导弹属于某个上升子序列,或是下降子序列;
如果属于上升子序列,则枚举属于哪个上升子序列(包括新开一个上升子序列);如果属于下降子序列,则枚举属于哪个下降子序列(包括新开一个下降子序列)。

#include <bits/stdc++.h>

using namespace std;

const int N = 55;

int a[N];
int up[N], down[N];
int n, ans;

// 遍历到了数组第u位,su是上升子序列的个数,sd是下降子序列的个数
void dfs(int u, int su, int sd)
{
    if (su + sd >= ans) return ;    //剪枝 当前所需系统数已经大于目前最优解直接返回
    if (u == n) ans = su + sd;      // 遍历结束,记录答案
    
    //上升子序列
    int k = 0;
    while (k < su && up[k] <= a[u]) k ++;   //找到第一个结尾大于a[u]的上升子序列
    int t = up[k];                          //记录之前up[k]的值
    up[k] = a[u];  
    if (k < su) dfs(u + 1, su, sd);         //k<su即找到的子序列是已经存在的
    else dfs(u + 1, su + 1, sd);            //k==su即新建了一个上升子序列
    up[k] = t;                              //回溯
    
    //下降子序列
    k = 0;
    while (k < sd && down[k] >= a[u]) k ++; //同上
    t = down[k];
    down[k] = a[u];
    if (k < sd) dfs(u + 1, su, sd);
    else dfs(u + 1, su, sd + 1);
    down[k] = t;
}

int main()
{
    while (cin >> n, n)
    {
        for (int i = 0; i < n; i ++) cin >> a[i];
        
        ans = n;            //ans最大为导弹个数
        dfs(0, 0, 0);       //遍历所有的情况
            
        cout << ans << endl;    //输出最优解
    }
    
    return 0;
}

二.最长公共上升子序列

最长公共子序列

897. 最长公共子序列
给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。
输入格式
第一行包含两个整数 N 和 M。
第二行包含一个长度为 N 的字符串,表示字符串 A。
第三行包含一个长度为 M 的字符串,表示字符串 B。
字符串均由小写字母构成。

f[i][j]的可能情况为01, 10, 00, 11

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n, m;
char a[N], b[N];
int f[N][N];

int main()
{
    cin >> n >> m;
    cin >> a + 1 >> b + 1;
    
    for (int i = 1; i <= n; i ++)
        for (int j = 1; j <= m; j ++)
        {
            f[i][j] = max(f[i - 1][j], f[i][j - 1]);
            if (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
        }
    
    cout << f[n][m];
    
    return 0;
}

最长公共上升子序列

272. 最长公共上升子序列
熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。

小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。

小沐沐说,对于两个数列 A 和 B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。

奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。

不过,只要告诉奶牛它的长度就可以了。

数列 A 和 B 的长度均不超过 3000。

朴素三重循环(可能会超时)

#include <bits/stdc++.h>

using namespace std;

const int N = 3010;

int n;
int a[N], b[N];
int f[N][N];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i <= n; i ++) cin >> b[i];
    
    for (int i = 1; i <= n; i ++)  
        for (int j = 1; j <= n; j ++)
        {
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j])
            {
                f[i][j] = max(f[i][j], 1);
                for (int k = 1; k < j; k ++)
                {
                    if (b[k] < b[j])
                        f[i][j] = max(f[i][j], f[i][k] + 1);
                }
            }
        }
    
    
    int res = 0;
    for (int i = 1; i <= n; i ++) res = max(res, f[n][i]);
    
    cout << res;
    
    return 0;
}

优化O(n^2):由于上面a[i] == b[j] 所以可以将下面的b[j]换为a[i],则就是i到j -1在满足b[k]小于a[i]的最大值,因此第三重循环与j没有关系了,我们可以将该循环提出,用maxv记录前缀最大值。

#include <bits/stdc++.h>

using namespace std;

const int N = 3010;

int n;
int a[N], b[N];
int f[N][N];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i <= n; i ++) cin >> b[i];
    
    for (int i = 1; i <= n; i ++)
    {
        int maxv = 1;
        for (int j = 1; j <= n; j ++)
        {
            f[i][j] = f[i - 1][j];
            if (a[i] == b[j]) f[i][j] = max(f[i][j], maxv);
            if (a[i] > b[j]) maxv = max(maxv, f[i][j] + 1);
        }
    }
    
    int res = 0;
    for (int i = 1; i <= n; i ++) res = max(res, f[n][i]);
    
    cout << res;
    
    return 0;
}

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值