这题有两问:①这套系统最多能拦截多少导弹(求最长不上升子序列);②拦截所有导弹最少要配备多少套这种导弹拦截系统(求最长上升子序列)。
1 朴素解法
数据规模达到105,朴素解法的两重循环会超时。
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1e5 + 5;
int dp1[maxn], dp2[maxn], a[maxn];
signed main() {
int n = 0;
while (cin >> a[++n]) {
dp1[n] = 1;
dp2[n] = 1;
for (int i = 1; i < n; i++)
if (a[i] >= a[n])
dp1[n] = max(dp1[i] + 1, dp1[n]);
for (int i = 1; i < n; i++)
if (a[i] < a[n])
dp2[n] = max(dp2[i] + 1, dp2[n]);
}
int ans1 = 0, ans2 = 0;
for (int i = 1; i < n; i++) {
ans1 = max(ans1, dp1[i]);
ans2 = max(ans2, dp2[i]);
}
cout << ans1 << endl << ans2;
return 0;
}
2 优化分析
2.1 朴素解法的缺陷
朴素解法,以求最长上升子序列为例:
cpp
for (int i = 1; i < n; i++)
if (a[i] < a[n])
dp2[n] = max(dp2[i] + 1, dp2[n]);
上面的核心代码通过找比 a n an an小且在 a n an an之前的数 a i ai ai,看看 a n an an接在 a i ai ai后面会不会比 a n an an之前的最长上升子序列长,如果是,则把 a n an an接在 a i ai ai后面,更新 d p 2 n dp2n dp2n的值,否则就不接。
这里的枚举会有很多没必要的比较,例如数据:1,2,3,4。对于4很明显接在3后面构成子序列1,2,3,4是最优解。在核心代码里面要和1,2,3比较, d p 2 1 dp21 dp21和 d p 2 2 dp22 dp22应该都是小于 d p 2 3 dp23 dp23的,因此会比较浪费时间。
2.2 优化思路
我们可以先排出一个上升子序列各个位置的较优数。比如,这组数65,170,155,300,299,158,207,389。一开始,上升序列什么都没有,第一个数是65,暂时把它放在第1位,我们可以开一个数组b,令 b 1 = 65 b1=65 b1=65;再看第二个数170,可以接在65后面,令 b 2 = 170 b2=170 b2=170;接着是第三个数155,比170小,不能往后接。但是,与170相比,把155作为上升子序列第二个数接在65后面,对于后面的数来说,可能更好接,形成更长的上升子序列,于是令 b 2 = 155 b2=155 b2=155。再接着看第四个数300,可以接在 b 2 b2 b2后面,令 b 3 = 300 b3=300 b3=300。之后看第五个数299,不能接在 b 3 b3 b3后面,但是,与300想比,把299接在65,155后面会更好,令 b 3 = 299 b3=299 b3=299。在之后第六个数158,同理,不能接在299后面,但是接在158后面,形成序列65,155,158对于后面的数延展更长的上升子序列更友好,故令 b 3 = 158 b3=158 b3=158。我们再看第七个数207,比158大,往后接, b 4 = 207 b4=207 b4=207。最后一个数389,比207大,就接在207后面, b 5 = 389 b5=389 b5=389。
概括一下刚才找最长上升子序列的过程,如果是第一个数,直接让他成为上升子序列的第一个数。如果不是,我们要先找到第一个大于等于当前的数的数,在上升子序列中,让当前的数替代第一个比它大或等于它的数。如果这个数比子序列的最大数还要大,就直接接在后面。b数组的下标即为最长上升子序列的长度。
同理,如果找的是最长不下降子序列,如果是第一个数,直接让他成为上升子序列的第一个数。如果不是,我们要先找到第一个比当前的数大的数,在上升子序列中,让当前的数替代第一个比它大的数。如果这个数比子序列的最大数还要大或等于子序列的最大数,就直接接在后面。b数组的下标即为最长上升子序列的长度。
这个过程中,设计在b数组中做查找。如果一个个遍历可能会超时,而我们观察到b数组是有序的,可以考虑二分查找。求最长上升子序列,查找的核心代码:
cpp
int l = 1, r = lenn;
while (l <= r) {
int mid = l + r >> 1;
if (a[n] > b[mid])
l = mid + 1;
else
r = mid - 1;
}
b[l] = a[n];
这个代码 l l l和 r r r都是可以取到的。比如一组数100,130,90,一开始 l = 1 , r = l e n n = 0 l=1,r=lenn=0 l=1,r=lenn=0,就得到 b 1 = 100 b1=100 b1=100;之后是130,一开始 l = 1 , r = 1 , m i d = 1 l=1,r=1,mid=1 l=1,r=1,mid=1,130>100, l = 2 l=2 l=2,跳出while循环, b 2 = 130 b2=130 b2=130。最后是90,一开始 l = 1 , r = 2 , m i d = 1 l=1,r=2,mid=1 l=1,r=2,mid=1,90<100, r = m i d − 1 = 1 r=mid-1=1 r=mid−1=1,说明区间在 m i d mid mid左边。接着是 l = 1 , r = 1 , m i d = 1 l=1,r=1,mid=1 l=1,r=1,mid=1,90<100, r = m i d − 1 = 0 r=mid-1=0 r=mid−1=0,结束while循环, b 1 = 90 b1=90 b1=90。
又如100,110,120,130,一开始 l = 1 , r = l e n n = 0 l=1,r=lenn=0 l=1,r=lenn=0,就得到 b 1 = 100 b1=100 b1=100;之后是110,一开始 l = 1 , r = 1 , m i d = 1 l=1,r=1,mid=1 l=1,r=1,mid=1,110>100, l = 2 l=2 l=2,跳出while循环, b 2 = 110 b2=110 b2=110。之后是120,一开始 l = 1 , r = 2 , m i d = 1 l=1,r=2,mid=1 l=1,r=2,mid=1,120>100, l = m i d + 1 = 2 l=mid+1=2 l=mid+1=2,说明区间在 m i d mid mid右边。接着是 l = 2 , r = 2 , m i d = 2 l=2,r=2,mid=2 l=2,r=2,mid=2,120>110, l = m i d + 1 = 3 l=mid+1=3 l=mid+1=3,然后比较 l l l和 r r r结束while循环, b 3 = 120 b3=120 b3=120。最后看130,一开始 l = 1 , r = 3 , m i d = 2 l=1,r=3,mid=2 l=1,r=3,mid=2,130>110, l = m i d + 1 = 3 l=mid+1=3 l=mid+1=3。接着 l = 3 , r = 3 , m i d = 3 l=3,r=3,mid=3 l=3,r=3,mid=3,130>120, l = m i d + 1 = 4 l=mid+1=4 l=mid+1=4,然后比较 l l l和 r r r结束while循环, b 4 = 130 b4=130 b4=130。
3 AC Code
cpp
#include <bits/stdc++.h>
using namespace std;
int n, dp[100005], a[100005], b[100005], c[100005];
int main() {
int lenn = 0, leno = 0;
c[leno] = 1e9;
while (cin >> a[++n]) {
if (a[n]) {
if (a[n] <= c[leno]) {
++leno;
c[leno] = a[n];
} else {
int l = 0, r = leno;
while (l <= r) {
int mid = l + r >> 1;
if (a[n] <= c[mid])
l = mid + 1;
else
r = mid - 1;
}
c[l] = a[n];
}
if (a[n] > b[lenn]) {
++lenn;
b[lenn] = a[n];
} else {
int l = 1, r = lenn;
while (l <= r) {
int mid = l + r >> 1;
if (a[n] > b[mid])
l = mid + 1;
else
r = mid - 1;
}
b[l] = a[n];
}
}
}
cout << leno << endl;
cout << lenn;
return 0;
}