每日三题-Day3-C(HDU 1257 最少拦截系统 最长上升子序列O(nlogn) )

原题地址

最少拦截系统

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 40250 Accepted Submission(s): 15708


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

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

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

Sample Input
  
  
8 389 207 155 300 299 170 158 65

Sample Output
  
  
2



思路:

在我第一次做这个题的时候,我是这么想的。

每一个拦截系统,可以拦截下非递增的导弹序列。我们当然要充分利用每一个拦截系统,所以题目变成了求,有多少个最长非递增子序列。

8 389 207 155 300 299 170 158 65

389 300 299 170 158 65 一个

207 155 一个

所以需要两个

怎么实现呢?

我使用O(n^2)的最长非递增子序列的算法

得到dp数组:

1 2 3 2 3 4 5 6

代表的是这个位置的数,他可以在最长非递增子序列里充当的最靠后的位置。
一个导弹系统,可以把不重复的数的导弹拦截,即是不重复的数可以串成一个最长非递增子序列。

比如上面的dp数组

1 2 3 2 3 4 5 6

* 2 3 * * * * *

下面打星号的可以串成一个最长非递增子序列(389 300 299 170 158 65

剩下的同样可以将不重复的数串成一个最长非递增子序列(207 155

因此,重复的数最多有多少个,说明我们需要多少个最长非递增子序列,即是拦截系统的数量。

不再给出代码。


上面的方法很是绕脑啊,在我做第二遍的时候,我发现了,其实这个可以用最长上升子序列来解。最长上升子序列的长度,就是拦截系统的数量。

因为导弹每上升一个高度,我们原来的导弹系统就没法拦截了,就需要一个新的拦截系统。所以这个题可以转化为最长上升子序列轻松解决。

不再给出代码。


前不久学习了O(nlogn)的最长上升子序列解法,我又尝试用这个做了一遍。

最长上升子序列O(nlogn)解法,其实已经不算动态规划了,算是贪心+二分。优点是利用二分将一个n降到了logn的时间。缺点是,我们不能还原出最长上升子序列的具体序列了。

O(nlogn)的解法,是用一个数组a[],a[i]代表长度为i的上升子序列的最后一位的最小值

如果第j个数,比a[i]大,说明它可以作为第i+1个数,上升子序列长度+1

否则,看看它能否更新a[1~i]里的最小值。因为a数组内的值是严格递增的,所以可以用二分查找降低时间复杂度。

样例中,a数组的变化如下:

num 389 207 155 300 299 170 158 65

a 389

  207

155

155 300

155 299

155 170

155 158

65 158


最后a数组的长度就是最长上升子序列的长度。

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
int ans;
int num[10010];
int dp[10010];

int dic(int L,int R,int x)
{
    if(L==R) return L;
    int mid=(L+R)>>1;
    if(dp[mid]<x) return dic(mid+1,R,x);
    else return dic(L,mid,x);
}

int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        for(int i=0; i<n; i++)
        {
            scanf("%d",&num[i]);
        }
        dp[0]=-INF;
        ans=0;
        for(int i=0; i<n; i++)
        {
            if(num[i]>dp[ans])
            {
                ans++;
                dp[ans]=num[i];
            }
            else
            {
                int p=dic(1,ans,num[i]);
                dp[p]=num[i];
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值