最少拦截系统 (dp:求最长递增子序列长度)

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

Input

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

Output

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

Sample Input

8 389 207 155 300 299 170 158 65

Sample Output

2

       一开始理解错题意了,以为一个系统的导弹是连续发射的(一次只用一个系统),就是找有几个非递增序列,这是错误的;后来突然意识到没那么简单,这其中有坑,那就是一个系统的导弹的发射可以不连续(因为是多个系统同时拦截,当前有能拦截的,那其他系统就不用发射了),这样当飞来一个导弹时,要看当前所有的系统能否拦截。

完整代码:

#include <iostream>
#include <cstdio>
#define int long long
const int maxn=3e4+5;
using namespace std;
int a[maxn],b[maxn];//a[i]表示依次飞来的导弹的高度,b[i]表示第i个拦截系统当前所能拦截的高度
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    while(cin>>n)
    {
        cin>>a[1];
        b[1]=a[1];
        int k=1;
        for(int i=2;i<=n;i++)
        {
            cin>>a[i];
            int flag=0;
            for(int j=1;j<=k;j++)
            {
                if(a[i]<=b[j])
                {
                    b[j]=a[i];//第j个拦截系统所能拦截的高度更新为a[i]
                    flag=1;//在当前所有系统中有能拦截的
                    break;
                }
            }
            if(flag==0)//当前所有拦截系统都不能拦截该高度a[i](a[i]超出当前所有拦截系统所能拦截的高度)
            {
                b[++k]=a[i];//这时要再加一个新的拦截系统(高度为a[i])
            }
        }
        cout<<k<<endl;
    }
    return 0;
}

    后来发现这并不是正解,这其实是一个dp(动态规划)问题,这个问题的实质其实是求最长非递减子序列的长度,因为需要新加一个系统的条件是当前的数比当前系统所能拦截的数的都大,所以最长非递减子序列每有一个元素就相当于要新加一个系统,因此用dp求最长非递减子序列的长度就行了。

下面我们来看一下dp的做法:

#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long
const int maxn=3e4+5;
using namespace std;
int a[maxn],dp[maxn],n,ans;

int maxlen()
{
    for(int i=0;i<n;i++)
    {
        dp[i]=1;
        for(int j=0;j<i;j++)
        {
            if(a[j]<a[i])
            {
                dp[i]=max(dp[i],dp[j]+1);//每次比较上一次加上a[i](+1加的就是当前的a[i])的原来以满足条件的a[j]为终点的递增序列长度(加上a[i]后长度+1)和当前加上a[i]的原来以a[j]为终点的递增序列长度
            }
        }
        ans=max(ans,dp[i]);//每次比较以a[i]为终点的最长递增子序列的长度,ans取其中的最大值
    }
    return ans;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    while(cin>>n)
    {
        ans=0;
        for(int i=0;i<n;i++)
        {
            cin>>a[i];
        }
        cout<<maxlen()<<endl;
    }
    return 0;
}

    然而上面这种简单的dp的时间复杂度是o(n^2),在数据很大的情况下会超时,这时就要对其进行优化,因为是求最长递增子序列,所以我们可以把递增的数依次放在dp[]数组中,然后遇到小的数,就二分查找其相应位置,这样就可以更新最大长度了,因此我们可以采用下面这种用二分查找优化的dp,时间复杂度就降到了o(n*logn)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define int long long
const int maxn=3e4+5;
using namespace std;
int a[maxn],dp[maxn],n,ans;

int maxlen()
{
    dp[1]=a[1];
    int len=1;
    for(int i=2;i<=n;i++)
    {
        if(a[i]>dp[len])
        {
            dp[++len]=a[i];//把递增的数依次放在dp[]数组中,数组的长度即为当前最长递增子序列的长度
        }
        else{
            int p=lower_bound(dp+1,dp+len+1,a[i])-dp;//返回dp[]中第一个大于等于a[i]的数的地址
            dp[p]=a[i];//覆盖掉相应的之前递增的数,从而组成新的最长递增子序列
        }
    }
    return len;//最终数组的长度即为最终最长递增子序列的长度
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    while(cin>>n)
    {
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        cout<<maxlen()<<endl;
    }
    return 0;
}
发布了89 篇原创文章 · 获赞 5 · 访问量 6693

猜你喜欢

转载自blog.csdn.net/Mr_Kingk/article/details/98481096