【NOIP1999】导弹拦截
本题在洛谷上的链接:https://www.luogu.org/problemnew/show/P1020
动态规划经典模型中有一类最长上升(不上升等等)子序列问题,这道题目算是对这一知识点的综合考察和拓展。以最长上升子序列为例,最简单易懂的做法是,定义状态dp[i]表示以第i个元素为结尾的最长上升子序列长度,若j>i且a[j]>a[i],则dp[j]=max(dp[j],dp[i]+1),否则dp[j]=max(dp[j],dp[i])。因为要枚举i和大于i的j,算法的复杂度近似为O(n^2)。还有一种效率更高的算法,定义ans数组并记录其长度,枚举i,对于每个a[i],若a[i]>ans[len],则ans[++len]=a[i],否则利用二分查找在a中找到第一个大于等于a[i]的元素,将其替换成a[i],最终的len就是答案,这样做复杂度仅为O(nlogn)。为什么是对的呢?不妨这样想,我们记录当前已生成的最长上升子序列,假如j>i且a[j]<a[i],那么将子序列中的a[i]替换成a[j]将不会有任何损失,反而会更优。
关于二分查找,可以使用STL中的lower_bound或upper_bound,但需要注意,这要求序列必须是从小到大排序,如果是求最长不上升子序列,就需要使用其第四个参数,自定义比较函数,但是也需要自己好好理理过程,以免出错,再就是可以自定义二分查找函数。
对于这道题,第一问显然是求最长不上升子序列,第二问怎么做呢?这里直接放上dilworth定理:一个序列最少的最长不上升子序列数量等于其最长上升子序列的长度。简单解释,假设我们已经将序列划分成了最少的几个最长不上升子序列,那么一定可以从每个中找到一个元素,新生成的序列一定单调上升(否则就最长不上升子序列的数量会减少),而且每个最长不上升子序列中只会被选出一个元素,否则单调性就会产生冲突,因此dilworth定理是对的。
1 #include <cstdio> 2 #include <algorithm> 3 4 using namespace std; 5 6 const int maxn = 1e5 + 5; 7 8 int h[maxn], dp[maxn]; 9 10 bool comp(const int& a, const int& b) { 11 return a >b; 12 } 13 14 int main() { 15 int in, n = 0, len = 0; 16 while (scanf("%d", &in) == 1) h[++n] = in; 17 for (int i = 1; i <= n; ++i) { 18 if (!len || h[i] <= dp[len]) dp[++len] = h[i]; 19 else { 20 int pos = upper_bound(dp + 1, dp + len + 1, h[i], comp) - dp; 21 dp[pos] = h[i]; 22 } 23 } 24 printf("%d\n", len); 25 len = 0; 26 for (int i = 1; i <= n; ++i) { 27 if (!len || h[i] > dp[len]) dp[++len] = h[i]; 28 else { 29 int pos = lower_bound(dp + 1, dp + len + 1, h[i]) - dp; 30 dp[pos] = h[i]; 31 } 32 } 33 printf("%d", len); 34 return 0; 35 }