动态规划: P1020 [NOIP1999 普及组] 导弹拦截 二分法求最大上升序列nlogn的方法
P1020 [NOIP1999 普及组] 导弹拦截
洛谷的一题普及/提高-的题目,考的知识点第一问是求序列中的最长不上升子序列的长度,第二问是求序列中不上升字序列的最少数量,根据 diworth定理,就是求最大上升子序列的长度。并且这题题目有个特殊的地方,题目满分有200分。
接下来 ,给大家讲一下我两种时间复杂度的代码。
一、O(n²)
求最长不上升子序列的长度和求最大上升子序列的长度,显然可以用二维数组线性DP实现,时间复杂度为O(n²) ;
上代码:
1 //洛谷 P1020 [NOIP1999 普及组] 导弹拦截 2 #include<iostream> 3 #include<cstring> 4 #include<sstream> 5 #include<vector> 6 using namespace std; 7 int dp1[100005]; 8 int dp2[100005]; 9 int main() 10 { 11 //先把数组读入string 再转化进int数组 12 string b; 13 getline(cin, b);//一定要用getline 否则读取不了空格 14 vector<int>a; 15 vector<string>c; 16 string temp; 17 stringstream room; 18 room.str(b); 19 int m1 = -1, m2 = -1; 20 while (room >> temp) 21 { 22 c.push_back(temp); 23 } 24 for (int i = 0; i < c.size(); ++i) 25 { 26 int t; 27 t = atoi(c[i].c_str()); 28 a.push_back(t); 29 dp1[i] = 1; 30 dp2[i] = 1;//dp数组初始化 31 } 32 33 //开始二维DP 34 for (int i = 0; i < a.size(); ++i) 35 { 36 for (int j = 0; j <= i - 1; ++j) 37 { 38 if (a[i] <= a[j]) 39 { 40 dp1[i] = max(dp1[j] + 1, dp1[i]); 41 } 42 if (a[i] > a[j]) 43 { 44 dp2[i] = max(dp2[j] + 1, dp2[i]); 45 } 46 } 47 m1 = max(dp1[i], m1); 48 m2 = max(dp2[i], m2);//直接在循环中每次比较一下最大值 最后直接输出就行 49 } 50 cout << m1 << endl << m2; 51 return 0; 52 }
提交代码后的测试数据:
果然只能过十个测试数据,拿到一百分,另外十个测试数据严重超时。所以需要时间复杂度的优化,才能拿到满分。
二、O(nlogn)
nlogn的做法,采用的是二分法,并且由于STL中自带二分的函数lower_bound和upper_bound ,可以大大简化算法。
以求最大上升子序列为例子,用本方法的原理:原存储数据的数组为a[n];我们创一个dp数组,dp[1]初始化为a[1];我们有一层循环遍历a[2-n];对于a的每一个元素,定义一个元素来描述dp数组的长度 :int len=1。
对a数组中的每个元素,做两个处理手段:
如果a[i]>dp[len],就把这个a[i]接到dp[len]后面,所以dp[++len]=a[i];
如果a[i]<=dp[len],就在dp数组中找到第一个比a[i]大的,替换之,我们可以这么像,a[i]更小,替换后,能让a[i]在dp中,使得以后便利的a[]可能比他大的更多,使序列更长,我们寻找这个元素用的是lower_bound(dp+1,dp+1+len,a[i]);这个函数,仔细一看格式还有点像sort。而且stl中有个强制的要求是要求我们的序列必须升序排序,所以如果这题是递减序列,也就是第一问所求的不上升序列的长度,要加一个cmp函数,或者自带的函数greater<int>(),upper_bound(dp1 + 1, dp1 + 1 + len1, a[i], greater<int>()) ,并且这个函数返回的是这个元素的地址,所以我们可以int 一个p,用p去接受函数返回的地址减去数组首地址,就是这个元素的数组下标,所以可以int p=lower_bound(dp+1,dp+1+len,a[i])-dp。然后dp[p]=a[i].//替换之。
但是很明显,这样子求出来的序列,dp数组中并不是最大上升序列的元素,只能是长度相等,原因是如果a[i]<=dp[len],我们替换,只是保证不影响长度,但是元素会发生变化。
这个方法在后面做最大上升序列,下降等等序列时非常好用,经常用到。
上代码:
1 //洛谷 P1020 [NOIP1999 普及组] 导弹拦截 2 #include<iostream> 3 #include<cstring> 4 #include<sstream> 5 #include<vector> 6 #include<algorithm> 7 #include<cmath> 8 using namespace std; 9 int dp1[100005]; 10 int dp2[100005]; 11 int main() 12 { 13 14 string b; 15 getline(cin, b); 16 vector<int>a; 17 vector<string>c; 18 string temp; 19 stringstream room; 20 room.str(b); 21 int len1=1, len2=1; 22 while (room >> temp) 23 { 24 c.push_back(temp); 25 } 26 for (int i = 0; i < c.size(); ++i) 27 { 28 int t; 29 t = atoi(c[i].c_str()); 30 a.push_back(t); 31 } 32 dp1[1] = a[0]; 33 dp2[1] = a[0]; 34 for (int i = 1; i < a.size(); ++i) 35 { 36 if (a[i] <= dp1[len1]) 37 { 38 dp1[++len1] = a[i]; 39 } 40 else 41 { 42 int p = upper_bound(dp1 + 1, dp1 + 1 + len1, a[i], greater<int>()) - dp1; 43 dp1[p] = a[i]; 44 } 45 if (a[i] > dp2[len2]) 46 { 47 dp2[++len2] = a[i]; 48 49 } 50 else 51 { 52 int p = lower_bound(dp2 + 1, dp2 + 1 + len2, a[i]) - dp2; 53 dp2[p] = a[i]; 54 } 55 } 56 cout << len1 << endl << len2; 57 58 return 0; 59 }
对比时间复杂度:
相差了二三十倍,所以nlogn的方法,十分好用!。、