zoukankan      html  css  js  c++  java
  • Luogu-P1020(导弹拦截)(DP,LIS ,二分优化)

    Luogu-P1020(导弹拦截)(DP)

    题意:

    给n(n<=100000) 个数字,求最长不上升子序列的长度和最少的不上升子序列的个数。

    分析:

    第一问:

    求最长不上升子序列有 O(n^2) 的做法,不过这里会超时。我们需要降低算法复杂度。

    j表示最长子序列的长度,然后d[i]储存以不上升子序列长度为 i 时结尾的最大数字。

    假如前 i -1 个数都已经检索完毕,已经找到了最长不上升子序列d[1]~d[j]

    然后对于第 i 个数a[i]

    • 如果a[i]<=d[j] 那么可以添加a[i]到当前最长不上升子序列的末尾。更新d[++j]=a[i]
    • 如果a[i]>d[j] 那么就要尝试把a[i]放到这个子序列中合适的位置,然后更新它。相当于是找到了以a[i]结尾的最长不上升子序列长度。
    1	2	3	4	5	6	7   8
    389 207 155 300 299 170 158  65
    
    
    第一步
    1
    389
    
    第二步
    1	2
    389 207
    
    第三步
    1	2	3
    389	207	155
    
    第四步(300 找到了 389 后面的位置,然后把207覆盖,这里为什么要覆盖呢?稍后解释)
    1	2	3	
    389	300	155	
    
    第五步
    1	2	3
    389	300	299
    
    第六步
    1	2	3	4
    389	300	299	170
    
    第七步
    1	2	3	4	5
    389	300	299	170	158
    
    第八步
    1	2	3	4	5	6
    389	300	299	170	158	 65
    

    第四步中,在原来的389 207 155序列中,如果要用 300 来做不上升子序列的结尾,那这个子序列的长度最长就是2,然后现在207在这个序列的第二个位置,所以我们应该换成更优的 300 来充当整个序列的不上升子序列长度为2 的末尾数,只有这样,才能保证最优(想一想为什么?只有当前末尾数更大,才更有可能在后面的更新中使得序列更长)。那么怎么找这个位置呢?二分。通过二分,就可以把这个算法复杂度降到nlogn。

    到此,第一问的解法已经解释完毕了。

    第二问:

    求一个序列里面最少有多少不上升子序列等于求这个序列里最长上升子序列的长度。这句话先入为主,然后就可以利用第一问的方法反着求就可以了。

    但是我们静下心来仔细想一想,我们如果用O(n^2)做,该怎么做?

    可以用一个数组d,d[i]表示第 i 个拦截系统当前的能打的最大高度。然后用一个变量 num 记录当前的拦截系统的个数。每次遇到a[i],从左到右遍历d,找到最小的j使得d[j]>=a[i]然后更新d[j] = a[i]。也就是找到一个高度最合适的拦截系统去拦截导弹。由于d数组是升序的,所以更新之后依然升序。如果不存在这样的j,那么意味着就要添加导弹d[++j] = a[i]

    咦!看到这里,你有没有觉得跟上一个问题特别相似。我们可以先检查d[num]>=a[i]是否成立,如果不成立,则需要增加拦截系统d[++num] = a[i]。如果成立,那么就需要二分找到最合适的位置去更新。

    那么这个求法,是不是就是在求最长上升子序列呢?

    int a[100000];
    int d[100000];
    int n=0;
    int l,r,mid;
    int main() 
    {
    	
        while(cin>>a[n++]);
        int j = 0;
        d[0] = a[0];
        n--;
        for(int i=1;i<n;i++)
        {
        	if(d[j]>=a[i])
                d[++j] = a[i];
            else
            {
                l = 0;r=j;
                while(l<r)
                {
                    mid = (l+r)>>1;
                    if(d[mid]>=a[i]) l = mid+1;
                    else r = mid;
                }
                d[l] = a[i];
            }
        }
        d[0] = a[0];
        int num = 0;
        for(int i=1;i<n;i++)
        {
        	if(d[num]<a[i])
            {
                d[++num] = a[i];continue;
            }
            l = 0;r = num;
            while(l<r)
            {
                mid = (l+r)>>1;//cout<<mid<<endl;
                if(d[mid]>=a[i]) r = mid;
                else l = mid+1;
            }
            d[r] = a[i];
        }
        cout<<j+1<<endl<<num+1<<endl;
        return 0;
    }
    
  • 相关阅读:
    LinuxShell脚本攻略--第八章 当个好管家
    LinuxShell脚本攻略--第六章 B计划
    LinuxShell脚本攻略--第三章 以文件之名
    LinuxShell脚本攻略--第二章 命令之乐
    LinuxShell脚本攻略--第一章 小试牛刀
    TCP/IP 与OSI结构图
    网络号和主机号的计算
    IP地址分类及私网IP
    转:Cache相关
    原码 反码 补码 移码
  • 原文地址:https://www.cnblogs.com/1625--H/p/9788556.html
Copyright © 2011-2022 走看看