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 }
总结:
- 想到答案增加的条件= =,而不是拿到一题就默写模板= =
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]的组尾.而这与要求的答案方法相同.