洛谷 导弹拦截(最大上升序列长度模板)||补充:【模板】最长公共子序列

这篇博客介绍了如何利用最长公共子序列算法解决导弹拦截问题,通过二分查找优化复杂度至O(NlogN)。同时,博主展示了如何将导弹拦截问题转化为最长上升子序列问题,并提供了相应的代码实现。此外,还探讨了如何在数字限制条件下,将最长公共子序列问题转换为最长上升子序列,进一步应用优化的算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 题目链接:

[NOIP1999 普及组] 导弹拦截 - 洛谷

【模板】最长公共子序列 - 洛谷

思路:

一. 导弹拦截

本题有两次子问题,一个是求最大不上升序列长度,一个是求最大上升序列长度。

第一个显然,第二个需要证明,我也是看别人的,证明如下:

(1)假设打导弹的方法是这样的:取任意一个导弹,从这个导弹开始将能打的导弹全部打完。而这些导弹全部记为为同一组,再在没打下来的导弹中任选一个重复上述步骤,直到打完所有导弹。

(2)假设我们得到了最小划分的K组导弹,从第a(1<=a<=K)组导弹中任取一个导弹,必定可以从a+1组中找到一个导弹的高度比这个导弹高(因为假如找不到,那么它就是比a+1组中任意一个导更高,在打第a组时应该会把a+1组所有导弹一起打下而不是另归为第a+1组),同样从a+1组到a+2组也是如此。那么就可以从前往后在每一组导弹中找一个更高的连起来,连成一条上升子序列,其长度即为K;

(3)设最长上升子序列长度为P,则有K<=P;又因为最长上升子序列中任意两个不在同一组内(否则不满足单调不升),则有

P>=K,所以K=P。(转自DP-导弹拦截 - JJPJJ 的博客 - 洛谷博客

至于做法,很容易想到O(N^2)的做法,但是还可以用二分思想优化到O(NlogN),这里写的是后者。

二. 最长公共子序列

本题需要设计一个O(NlogN)的算法,一般的该类问题是做不到的,但是这题有条件:都是1~n的数,那么就可以特殊处理,比如第一组数是31245,设计映射为abcde。第二组是12345,根据第一组数的num->char的映射,得到映射之后的结果bcade。(实际操作是用数字映射)

然后就能转化成求第二组数的最长上升子序列问题啦,解法同上。

代码:

一. 导弹拦截

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int arr[maxn], d1[maxn], d2[maxn];
int len1 = 1, len2 = 1;
int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n=1; while(cin>>arr[n]) n++;
    n--; //长度调整
    d1[1] = arr[1]; d2[1] = arr[1]; //d数组初始化
    for(int i=2; i<=n; i++){
        //d1数组,记录不上升序列
        if(arr[i]<=d1[len1]) d1[++len1] = arr[i]; //接到后面
        else{
            int p1 = upper_bound(d1+1, d1+len1+1, arr[i], greater<int>())-d1; //注意用降序比较器
            d1[p1] = arr[i]; //改写前面的数
        }
        //再处理d2数组,记录上升序列
        if(arr[i]>d2[len2]) d2[++len2] = arr[i];
        else{
            int p2 = lower_bound(d2+1, d2+len2+1, arr[i])-d2;
            d2[p2] = arr[i]; //改写前面的数
        }
    }
    cout<<len1<<'\n'<<len2;
}

二. 最长公共子序列

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int n;
int mp[maxn], a[maxn]; //映射数组,a2数组
int d[maxn];
int main(){
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n;
    for(int i=1; i<=n; i++){
        int x; cin >> x;
        mp[x] = i; //反向记录映射
    }
    for(int i=1; i<=n; i++) {
        cin >> a[i];
        a[i] = mp[a[i]]; //直接记录映射之后的结果
    }
    int len = 1; d[1] = a[1]; //d数组初始化
    for(int i=2; i<=n; i++){
        if(a[i] > d[len]) d[++len] = a[i]; //如果当前数最大,直接放在后面
        else{
            int p = lower_bound(d+1,d+len+1,a[i])-d; //在d数组内寻找第一个 >= a[i]的数字
            d[p] = a[i]; //放在d数组中间的适当位置
        }
    }
    cout << len;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值