AcWing 1010. 拦截导弹

原题链接

考察:线性dp

思路:

        第一问是套模板.

        第二问的思路是要想到增加序列个数的条件.由贪心法,我们求不上升子序列的组数.对于一个数x,假设已有两组a,b.如果a,b内没有比x更大的数.说明我们需要重新开辟一组.如果x较小,因为不允许插入,所以我们只能在末尾加入元素,我们要保证序列个数最少,就要保证接到后面的数字尽量多,因为是下降序列,所以末尾数字要尽量大,所以我们要找到最小的>x的数,而不破坏其他>>>x的序列

        这个贪心思路怎么证明是正确的呢?假设当前两组a,b.设由贪心思路我们加到a组内,最优解加到b组内.由上文,x的前一个数必定是所有数里最小的>x的数.而最优解前一个数必定也大于x.可以发现最优解的x存放处也可以放到贪心解里.由此不断互换,可以发现贪心解法就是正解.

        解法:用数组存储每个序列的末尾,找到>x的数,将x更新为新的序列末尾.可以发现数组内就是一个单调递增的序列(上升子序列).这步可以用二分优化.最后数组内的元素个数就是答案.

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 using namespace std;
 6 const int N = 1010;
 7 int a[N],f[N],cnt;
 8 int main()
 9 {
10     int n = 0,len = 0;
11     while(scanf("%d",&a[n+1])!=EOF) n++;
12     for(int i=1;i<=n;i++)
13     {
14         int l = 0,r = cnt;
15         while(l<r)
16         {
17             int mid = l+r+1>>1;
18             if(f[mid]<a[i]) l = mid;
19             else r = mid-1;
20         }
21         f[l+1] = a[i];
22         cnt = max(cnt,r+1);
23     }
24     for(int i=n;i>=1;i--)
25     {
26         int l = 0,r = len;
27         while(l<r)
28         {
29             int mid = l+r+1>>1;
30             if(f[mid]<=a[i]) l = mid;
31             else r = mid-1;
32         }
33         f[l+1] = a[i];
34         len = max(len,r+1);
35     }
36     printf("%d\n%d\n",len,cnt);
37     return 0;
38 }

 

总结:

  1. 想到答案增加的条件= =,而不是拿到一题就默写模板= =

2021.3.12 二刷,有了更深的感悟:

          假设现在x组,如果每一组的组尾都<a[i],那么x++,如果存在组尾>=a[i].根据贪心原则,我们要让a[i]接到最接近a[i]且>=a[i]的组尾处.我们回想一下二分求最长子序列,就是二分找最靠近a[i]且<a[i]的x,然后将a[i]覆盖下一组的组尾.而下一组的组尾必然>=a[i],也即是要找最靠近a[i]且>=a[i]的组尾.而这与要求的答案方法相同.

posted @ 2021-02-06 14:41  acmloser  阅读(90)  评论(0编辑  收藏  举报