题目链接:
思路:
一. 导弹拦截
本题有两次子问题,一个是求最大不上升序列长度,一个是求最大上升序列长度。
第一个显然,第二个需要证明,我也是看别人的,证明如下:
(1)假设打导弹的方法是这样的:取任意一个导弹,从这个导弹开始将能打的导弹全部打完。而这些导弹全部记为为同一组,再在没打下来的导弹中任选一个重复上述步骤,直到打完所有导弹。
(2)假设我们得到了最小划分的K组导弹,从第a(1<=a<=K)组导弹中任取一个导弹,必定可以从a+1组中找到一个导弹的高度比这个导弹高(因为假如找不到,那么它就是比a+1组中任意一个导更高,在打第a组时应该会把a+1组所有导弹一起打下而不是另归为第a+1组),同样从a+1组到a+2组也是如此。那么就可以从前往后在每一组导弹中找一个更高的连起来,连成一条上升子序列,其长度即为K;
(3)设最长上升子序列长度为P,则有K<=P;又因为最长上升子序列中任意两个不在同一组内(否则不满足单调不升),则有
P>=K,所以K=P。(转自DP-导弹拦截 - JJPJJ 的博客 - 洛谷博客)
至于做法,很容易想到O(N^2)的做法,但是还可以用二分思想优化到O(NlogN),这里写的是后者。
二. 最长公共子序列
本题需要设计一个O(NlogN)的算法,一般的该类问题是做不到的,但是这题有条件:都是1~n的数,那么就可以特殊处理,比如第一组数是31245,设计映射为abcde。第二组是12345,根据第一组数的num->char的映射,得到映射之后的结果bcade。(实际操作是用数字映射)
然后就能转化成求第二组数的最长上升子序列问题啦,解法同上。
代码:
一. 导弹拦截
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int arr[maxn], d1[maxn], d2[maxn];
int len1 = 1, len2 = 1;
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n=1; while(cin>>arr[n]) n++;
n--; //长度调整
d1[1] = arr[1]; d2[1] = arr[1]; //d数组初始化
for(int i=2; i<=n; i++){
//d1数组,记录不上升序列
if(arr[i]<=d1[len1]) d1[++len1] = arr[i]; //接到后面
else{
int p1 = upper_bound(d1+1, d1+len1+1, arr[i], greater<int>())-d1; //注意用降序比较器
d1[p1] = arr[i]; //改写前面的数
}
//再处理d2数组,记录上升序列
if(arr[i]>d2[len2]) d2[++len2] = arr[i];
else{
int p2 = lower_bound(d2+1, d2+len2+1, arr[i])-d2;
d2[p2] = arr[i]; //改写前面的数
}
}
cout<<len1<<'\n'<<len2;
}
二. 最长公共子序列
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int n;
int mp[maxn], a[maxn]; //映射数组,a2数组
int d[maxn];
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n;
for(int i=1; i<=n; i++){
int x; cin >> x;
mp[x] = i; //反向记录映射
}
for(int i=1; i<=n; i++) {
cin >> a[i];
a[i] = mp[a[i]]; //直接记录映射之后的结果
}
int len = 1; d[1] = a[1]; //d数组初始化
for(int i=2; i<=n; i++){
if(a[i] > d[len]) d[++len] = a[i]; //如果当前数最大,直接放在后面
else{
int p = lower_bound(d+1,d+len+1,a[i])-d; //在d数组内寻找第一个 >= a[i]的数字
d[p] = a[i]; //放在d数组中间的适当位置
}
}
cout << len;
}