洛谷 P1020 导弹拦截(LIS)

传送门

 

懵懂的题解

深入理解

 

参考资料:

  [1]:LIS详解1

  [2]:LIS详解2

 

相关概念解释:

  1.串 & 子序列

    一个串的子串是指该串的一个连续的局部。

    如果不要求连续,则可称为它的子序列。

    比如对串: "abcdefg" 而言,"ab","abd","bdef" 等都是它的子序列。  

    特别地,一个串本身,以及空串也是它的子序列。

  2.最长上升子序列 & 最长不下降子序列

    最长上升子序列(Longest Increasing Subsequence,LIS),在计算机科学上是指一个序列中最长的单调递增的子序列。

    而最长不下降子序列则不一定要保证单调递增,只要保证  a[ i ] <= a[ j ] ( j > i , 且在序列范围内)即可。

现在开始回归主题:

题意:

  中文题意,不再赘述;

题解:

  第一问求最长不上升子序列的长度;

  第二问求这个序列里面最少有多少最长不上升子序列。

  难点就在与第二问,如何求呢?

  看大佬博客说“求一个序列里面最少有多少最长不上升序列等于求这个序列里最长上升序列的长度”,我也不懂为啥 /小纠结,或许,这就是大佬吧。

废话少说,上AC代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define INF 0x3f3f3f3f
 4 const int maxn=1e5+50;
 5 
 6 int n;
 7 int a[maxn];
 8 int dp[maxn];
 9 
10 void Solve()
11 {
12     int len=0;
13     dp[len]=INF;
14     for(int i=1;i <= n;++i)
15     {
16         if(a[i] <= dp[len])
17             dp[++len]=a[i];
18         else
19         {
20             int l=0,r=len+1;
21             while(r-l > 1)//二分出最后一个不小于 a[i] 的下标
22             {
23                 int mid=(l+r)/2;
24                 if(dp[mid] >= a[i])//注意,此处取到"=="判给了 l
25                     l=mid;
26                 else
27                     r=mid;
28             }
29             dp[r]=a[i];
30         }
31     }
32     printf("%d\n",len);
33     len=0;
34     dp[len]=-1;
35     for(int i=1;i <= n;++i)//求最长上升子序列
36     {
37         if(a[i] > dp[len])
38             dp[++len]=a[i];
39         else
40         {
41             int t=lower_bound(dp+1,dp+len+1,a[i])-dp;
42             dp[t]=a[i];
43         }
44     }
45     printf("%d\n",len);
46 }
47 
48 int main()
49 {
50     while(~scanf("%d",&a[++n]))//以回车结束输入的输入方式
51         continue;
52     n--;
53     Solve();
54 }
View Code

bug:

  (1):输入方式

  正确的输入方式:

1 while(~scanf("%d",&a[++n]))//以回车结束输入的输入方式
2     continue;
3 n--;//最后一个 n 接受的是 '\n' ,所以需要减去
View Code

  错误的输入方式,返回 RE,至今不知道为啥,有知道的大佬可否告知一二%%%%%%%%

1 do
2 {
3     scanf("%d",&a[++n]);
4 }while(getchar() != '\n');
View Code

  第一种输入方式在本地无法测试,但 OJ 可以。

  第二种输入方式,虽然本地可以测试,但提交后返回 RE,应该是输入停不下来的吧。

 


分割线:2019.6.3

对第二问有了深一步的理解;

第二问求得是最少需要多少导弹拦截系统,根据贪心的思想,每个导弹系统都希望可以拦截尽可能多的导弹;

那么第一个导弹拦截系统最多可以拦截 cnt1 个,cnt1 = 最长不上升子序列的长度;

第二个导弹拦截系统最多可拦截 cnt2 个,cnt2 = 去掉第一次拦截的导弹后的最长不上升子序列的长度;

.............

第x个导弹拦截系统最多可拦截 cntx 个,cntx = 去掉前(x-1)次拦截的导弹后的最长不上升子序列长度;

假设第一次拦截的最低的导弹高度为 a1

第二次拦截的最低的导弹高度为 a2;

..............

第x次拦截的最低导弹高度为 ax

那么,a1 < a2 < ... < ax

用反证法证明:

  假设 ai > aj ( i<j );

  那么,在第 i 次拦截中结尾的不应该是 ai,而应该是比 ai 还小的 aj

  但已经假设 ai 是第 i 次拦截中的最低导弹高度,与假设矛盾;

所以,第二问求的是最长上升子序列;

AC代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define INF 0x3f3f3f3f
 4 const int maxn=1e5+50;
 5 
 6 int n;
 7 int a[maxn];
 8 int tmp[maxn];
 9 int d[maxn];
10 
11 void Solve()
12 {
13     int k=n;
14     for(int i=1;i <= n;++i)
15         tmp[i]=a[k--];
16 
17     int cnt=0;
18     d[cnt]=-1;
19     for(int i=1;i <= n;++i)
20     {
21         if(tmp[i] >= d[cnt])
22             d[++cnt]=tmp[i];
23         else
24         {
25             int t=upper_bound(d+1,d+cnt+1,tmp[i])-d;
26             d[t]=tmp[i];
27         }
28     }
29     printf("%d",cnt);
30 
31     cnt=0;
32     for(int i=1;i <= n;++i)
33     {
34         if(a[i] > d[cnt])
35             d[++cnt]=a[i];
36         else
37         {
38             int t=lower_bound(d+1,d+cnt+1,a[i])-d;
39             d[t]=a[i];
40         }
41     }
42     printf(" %d\n",cnt);
43 
44     return ;
45 }
46 int main()
47 {
48 //    freopen("C:/Users/14685/Desktop/stdin&&stdout/contest","r",stdin);
49     n=0;
50     while(~scanf("%d",&a[++n]));
51     n--;
52     Solve();
53 
54     return 0;
55 }
View Code

 

posted @ 2018-10-25 20:18  HHHyacinth  阅读(444)  评论(0编辑  收藏  举报