动态规划: 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的方法,十分好用!。、

 

posted @ 2022-04-07 10:54  朱朱成  阅读(225)  评论(0编辑  收藏  举报