1010. 拦截导弹(贪心+队列,lis)

本文探讨了通过最长下降子序列算法优化防御系统配置的问题。首先介绍了如何计算单个防御设备能拦截的最大导弹数量,接着讨论了利用最少设备实现全面覆盖的策略。采用贪心算法和队列思想,提出两种优化方案,并通过代码示例详细解析。

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

题目分为两问

1.一套防御设备能够拦截的最大导弹数

2.需要配备几个防御设备

第一问比较简单,就是一个简单的求最长下降子序列(最长不上升子序列)

第二问求最少防御设备数,要用最少的下降子序列对数组进行全覆盖,根据贪心+队列的思想,就是求最小队列数

相似:P4447 [AHOI2018初中组]分组_qq12323qweeqwe的博客-CSDN博客

贪心思路

贪心思想的证明

1.贪心得到的队列数是否==最优解

2.证明得到的队列==最优解队列      

1.证明队列数

 

 如图,a[i]=4,能够插入的只有i,i+1,贪心解插入的是当前所有可以插入的队列中,长度最小的队列,即q[i],而最优解插入的是q[i+1]

这种最优决策使得当前局面差于贪心决策(会导致每个队列长度不会很平均),所以贪心解<=最优解

 由于贪心解>=最优解,可以证明贪心解==最优解

2.证明队列(调整法)

证明队列数一样之后,发现每个队列互不相同

 对于所有两个队列不同的某个元素,可以进行交换,进行有限次交换后,可以把贪心策略调整为最优解的策略

如果是一串序列不一致,那么也可以直接交换一串,获得最优解

如果长度不一,可以以少数交换多数,由于每次交换都不会使得两个序列的总长度增加

于是,由该决策包容性,得出最优解可以是贪心解。

dp+dp

由上述问题2的分析,只有不能插入时(a[i]大于所有子序列的结尾)才会增加队数,其实就是最长上升子序列

#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e3+10;
int a[N];
int dp[N];
int len[N];
int q[N];
int cnt;


int main()
{
    int n=0;
    while(cin>>a[n])
        n++;
    int res=0;
    for(int i=0;i<n;i++)
    {
        dp[i]=1;
        for(int j=0;j<i;j++)
            if(a[i]<=a[j])
                dp[i]=max(dp[i],dp[j]+1);
        res=max(res,dp[i]);
    }
    cout<<res<<'\n';
    int res2=0;
    for(int i=0;i<n;i++)
    {
        dp[i]=1;
        for(int j=0;j<i;j++)
            if(a[i]>a[j])
                dp[i]=max(dp[i],dp[j]+1);
        res2=max(res2,dp[i]);
    }
    cout<<res2;
}

贪心+二分 (O(nlogn))

贪心的做法有两种

1.求队列数时贪心

2.已经队列数==最长上升子序列,对最长上升子序列优化

1.求队列数时优化

求队列数,即求最少需要配备几个防御设备,上面已经证明了

这里要注意的是,求最小组数,他每一组并不一定能够得到最长上升子序列,每一组可能是平均的。

关于求最少组数的时候

我们每一个组的内部都是一个最长不上升子序列(下降)

每次插入都会导致qq中某个组的值减少(存储最后一个元素)

那么如图我们应该插入第一个可以插入的元素,才可以保持单调性

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e3 + 10;
int a[N];
int len[N];
int qq[N];  //存储第一问答案,最长不上升子序列
int q[N];   //存储第二问答案,最长上升子序列
int cnt;
int Binary_search(int x)
{//找到所有可以插入的队列中长度最小的队列
 //长度最小的队列==第一个>=a[i]的队列
    int l = 0, r = cnt - 1;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (qq[mid] > x)
            r = mid;
        else if (qq[mid] == x)
            r = mid;
        else
            l = mid + 1;
    }
    return qq[l] >= x ? l : -1;
}
int main()
{
    int n = 0;
    while (cin >> a[n])
        n++;
    int res = 0;
    q[0] =2e9;
    //找到最长不上升子序列
    for (int i = 0;i < n;i++)
    {
        //找到最后一个>=a[i]的数字
        int l = 0, r = res;
        while (l < r)
        {
            int mid = l + r+1 >> 1;
            if (q[mid] >= a[i])
                l = mid;
            else
                r = mid - 1;
        }
        res = max(res, l + 1);
        q[l + 1] = a[i];
    }
    cout << res << endl;
    //求组数
    for (int i = 0;i < n;i++)
    {
        //求最小组数其实就是模拟分组的过程,这样子得出来的组数一定是最优解
        //但是不能保证得到最长不上升子序列
        int j = Binary_search(a[i]);
        if (!cnt || j == -1)
        {
            qq[cnt] = a[i];
            len[cnt]++;
            cnt++;
        }
        else
            qq[j] = a[i], len[j]++;
    }
    cout << cnt;
}

2.分别优化最长不上升子序列和最长上升子序列 

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e3 + 10;
int a[N];
int len[N];
int qq[N];  //存储第一问答案,最长不上升子序列
int q[N];   //存储第二问答案,最长上升子序列
int cnt;

int main()
{
    int n = 0;
    while (cin >> a[n])
        n++;
    int res = 0;
    q[0] =2e9;
    //找到最长不上升子序列
    for (int i = 0;i < n;i++)
    {
        //找到最后一个>=a[i]的数字
        int l = 0, r = res;
        while (l < r)
        {
            int mid = l + r+1 >> 1;
            if (q[mid] >= a[i])
                l = mid;
            else
                r = mid - 1;
        }
        res = max(res, l + 1);
        q[l + 1] = a[i];
    }
    cout << res << endl;
    res = 0;
    qq[0] = -2e9;
    for (int i = 0;i < n;i++)
    {
        //找到最后一个<a[i]的数字
        int l = 0, r = res;
        while (l < r)
        {
            int mid = l + r + 1 >> 1;
            if (qq[mid] > a[i])
                r = mid - 1;
            else if (qq[mid] == a[i])
                r = mid - 1;
            else
                l = mid;
        }
        res = max(res, l + 1);
        qq[l + 1] = a[i];
    }
    cout << res;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值