洛谷链接:https://www.luogu.org/problem/P1020
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是\le 50000≤50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
11行,若干个整数(个数\le 100000≤100000)
输出格式
22行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入输出样例
输入 #1389 207 155 300 299 170 158 65输出 #1
6 2
说明/提示
为了让大家更好地测试n方算法,本题开启spj,n方100分,nlogn200分
每点两问,按问给分
题解
这道题现在基本上已经是DP的入门问题了。
第一问实际是求一个最长不降子序列,而第二问是求最长上升子序列。
最容易理解的O(n2)的解法,而O(nlogn)的解法就费解一些了。关于这部分的原理写得最通俗易懂的是a342374071的文章:https://blog.csdn.net/a342374071/article/details/6694452。
下面先贴上O(n2)的代码。
1 #include <iostream> 2 #include <stdio.h> 3 #include <math.h> 4 #include <algorithm> 5 #include <string.h> 6 7 using namespace std; 8 9 const int MAXN = 1e5 + 10; 10 int n = 1, a[MAXN], d[MAXN], dp[MAXN]; 11 12 int main() 13 { 14 int ans1 = 0, ans2 = 0; 15 while(scanf("%d", &a[n]) != EOF) 16 { 17 ++n; 18 } 19 --n; 20 for(int i = 1; i <= n; i++) 21 { 22 d[i] = 1; 23 for(int j = 1; j < i; j++) 24 { 25 if(a[j] >= a[i]) 26 { 27 d[i] = max(d[i], d[j] + 1); 28 } 29 } 30 if(ans1 < d[i]) 31 { 32 ans1 = d[i]; 33 } 34 } 35 for(int i = 1; i <= n; i++) 36 { 37 dp[i] = 1; 38 for(int j = 1; j < i; j++) 39 { 40 if(a[j] < a[i]) 41 { 42 dp[i] = max(dp[i], dp[j] + 1); 43 } 44 } 45 if(ans2 < dp[i]) 46 { 47 ans2 = dp[i]; 48 } 49 } 50 cout << ans1 << endl; 51 cout << ans2 << endl; 52 return 0; 53 }
下面是O(nlogn)的代码:
1 #include <iostream> 2 #include <stdio.h> 3 #include <math.h> 4 #include <algorithm> 5 #include <string.h> 6 7 using namespace std; 8 9 const int MAXN = 1e5 + 10; 10 int n = 1, a[MAXN], d[MAXN], dp[MAXN], ans1 = 0, ans2 = 0, mid, l, r; 11 12 int main() 13 { 14 while(scanf("%d", &a[n]) != EOF) 15 { 16 ++n; 17 continue; 18 } 19 --n; 20 d[1] = a[1]; 21 dp[1] = a[1]; 22 ans1 = ans2 = 1; 23 for(int i = 2; i <= n; i++) 24 { 25 if(a[i] <= d[ans1]) 26 { 27 ans1++; 28 d[ans1] = a[i]; 29 } 30 else 31 { 32 l = 1, r = ans1; 33 while(l < r) 34 { 35 mid = (l + r) / 2; 36 if(a[i] > d[mid]) 37 { 38 r = mid; 39 } 40 else 41 { 42 l = mid + 1; 43 } 44 } 45 d[l] = a[i]; 46 } 47 } 48 49 for(int i = 2; i <= n; i++) 50 { 51 if(a[i] > dp[ans2]) 52 { 53 ans2++; 54 dp[ans2] = a[i]; 55 } 56 else 57 { 58 l = 1, r = ans2; 59 while(l < r) 60 { 61 mid = (l + r) / 2; 62 if(a[i] <= dp[mid]) 63 { 64 r = mid; 65 } 66 else 67 { 68 l = mid + 1; 69 } 70 } 71 dp[l] = a[i]; 72 } 73 } 74 cout << ans1 << endl; 75 cout << ans2 << endl; 76 return 0; 77 }
这段代码中有个细节需要注意,就是当二分查找时等号应该放在if条件里,还是放在else里面。例如,在求不升子序列时,因为希望a[i]和d[mid]相等,则希望把a[i]替换d[mid]右侧的数据,这样可以使d[mid]右侧的数据尽可能大,从而使序列更长。所以程序里面的两个比较条件非常重要,如果疏忽了会导致WA。