题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。
但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。
某天,雷达捕捉到敌国的导弹来袭。
由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
共一行,输入导弹依次飞来的高度。
输出格式
第一行包含一个整数,表示最多能拦截的导弹数。
第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。
数据范围
雷达给出的高度数据是不大于 30000 的正整数,导弹数不超过 1000。
输入样例
389 207 155 300 299 170 158 65
输出样例
6
2
算法思想(线性动规+贪心)
根据题目描述:导弹依次飞来,拦截时每一发炮弹都不能高于前一发的高度。那么最多能拦截的导弹数,其实就是最长的下降(非严格下降)子序列的长度。
第二问求最少要配备多少套这种导弹拦截系统?可以使用贪心思想,流程如下:
从前向后遍历每一个导弹高度,对于每一个数来说,有两种情况:
- 所有拦截系统的最低高度都小于当前导弹高度,需要增加一套系统
- 找到第一套系统,其拦截的最低高度大于等于当前导弹的高度,更新该系统的最低拦截高度。
代码实现
#include <iostream>
using namespace std;
const int N = 1010, INF = 0x3f3f3f3f;
int a[N], f[N], b[N];
int main(){
int n = 0, x;
while(cin>>x) a[++n] = x;
int res = 0;
for(int i = 1; i <= n; i++){
f[i] = 1;
for(int j = 1; j < i; j++){
if(a[j] >= a[i]) f[i] = max(f[i], f[j] + 1);
}
if(res < f[i]) res = max(res, f[i]);
}
cout<<res<<endl;
int cnt = 0;
for(int i = 1; i <= n; i++){
int k = 0;
//b[k]表示第k套拦截系统能够拦截的最小高度,并且b[]序列一定是严格单调递增的
while(k < cnt && b[k] < a[i]) k++;
if(k >= cnt) cnt++;
b[k] = a[i];
}
cout<<cnt<<endl;
return 0;
}
扩展1:狄尔沃斯定理(Dilworth’s theorem)
原链最大长度 等于 反链划分数最小值。
原链 | 反链 |
---|---|
(严格)上升子序列 | 不上升子序列 |
不上升子序列 | (严格)上升子序列 |
(严格)下降子序列 | 不下降子序列 |
不下降子序列 | (严格)下降子序列 |
因此,本题中第二问求最少要配备多少套这种导弹拦截系统(即不上升子序列的划分数),可以转变为求最长(严格)上升子序列的最大长度。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 50010;
int a[N], f[N], g[N];
int main()
{
int n = 0, x;
while(cin >> x) a[++ n] = x;
int ans = 0;
for(int i = 1; i <= n; i ++)
{
f[i] = 1;
for(int j = 1; j < i; j ++)
{
if(a[j] >= a[i]) f[i] = max(f[i], f[j] + 1);
}
ans = max(ans, f[i]);
}
cout << ans << endl;
//根据狄尔沃斯定理,求反链(最长上升子序列)的最大长度
ans = 0;
for(int i = 1; i <= n; i ++)
{
f[i] = 1;
for(int j = 1; j < i; j ++)
{
if(a[j] < a[i]) f[i] = max(f[i], f[j] + 1);
}
ans = max(ans, f[i]);
}
cout << ans << endl;
}
扩展2:贪心 + 二分
算法思想
对于序列 ( 389 , 207 , 155 , 300 , 299 , 170 , 158 , 65 ) (389,207,155,300,299,170,158, 65) (389,207,155,300,299,170,158,65)
使用 q [ ] q[] q[]存储不同长度的不上升序列中最后一个数的最大值,例如:
- 长度为1的所有不上升序列中,结尾最大的是 ( 389 ) (389) (389),因此 q [ 1 ] = 389 q[1] = 389 q[1]=389
- 长度为2的所有不上升序列中,结尾最大的是 ( 389 , 300 ) (389, 300) (389,300),因此 q [ 2 ] = 300 q[2] = 300 q[2]=300
- 长度为3的所有不上升序列中,结尾最大的是 ( 389 , 300 , 299 ) (389,300,299) (389,300,299),因此 q [ 3 ] = 299 q[3] = 299 q[3]=299
- 长度为4的所有不上升序列中,结尾最大的是 ( 389 , 300 , 299 , 170 ) (389,300,299,170) (389,300,299,170),因此 q [ 4 ] = 170 q[4] =170 q[4]=170
- 长度为5的所有不上升序列中,结尾最大的是 ( 389 , 300 , 299 , 170 , 158 ) (389,300,299,170,158) (389,300,299,170,158),因此 q [ 5 ] = 158 q[5] =158 q[5]=158
- 长度为6的所有不上升序列中,结尾最大的是 ( 389 , 300 , 299 , 170 , 158 , 65 ) (389,300,299,170,158,65) (389,300,299,170,158,65),因此 q [ 6 ] = 65 q[6] =65 q[6]=65
通过观察,发现 q [ ] q[] q[]是严格单调递减的序列,可以使用二分进行查找。
算法实现
通过构造 q [ ] q[] q[],求出最长不上升子序列的长度 m m m:
- 遍历序列中的每个数
a
[
i
]
a[i]
a[i]
- 通过二分查找,在
q
[
]
q[]
q[]数组中找到最后一个大于等于
a
[
i
]
a[i]
a[i]的位置
L
L
L
- 将 q [ L + 1 ] q[L + 1] q[L+1]更新为 a [ i ] a[i] a[i]
- 若 L + 1 > m L + 1 > m L+1>m,则更新 m m m
- 通过二分查找,在
q
[
]
q[]
q[]数组中找到最后一个大于等于
a
[
i
]
a[i]
a[i]的位置
L
L
L
- 输出 m m m
代码实现
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10, INF = 1e9;
int a[N], f[N], q[N];
int main()
{
int n = 0, x, len;
while(cin >> x) a[++ n] = x;
//贪心+二分求最长不上升子序列长度
//q[len]表示长度为len的最长不上升子序列最后一个元素
//即满足长度为len时的最长不上升子序列最后一个元素的最大值
//序列q[]为一个不上升的序列
len = 0; q[len] = INF;
for(int i = 1; i <= n; i ++)
{
if(a[i] <= q[len]) q[++ len] = a[i];
else
{
int L = 0, R = len, mid;
while(L < R)
{
//二分查找q[]序列中最后一个大于等于a[i]的位置L
mid = (L + R + 1) / 2;
if(q[mid] >= a[i]) L = mid;
else R = mid - 1;
}
q[L + 1] = a[i]; //注意,更新的L + 1位置的值
}
}
cout << len << endl;
//根据狄尔沃斯定理,要求反链(不上升子序列)的最小划分数
//即求原链(最长上升子序列)的最大长度
len = 0; q[len] = -INF;
//q[len]表示长度为len的最长严格上升子序列最后一个元素
//即满足长度为len时的最长严格上升子序列最后一个元素的最小值
//序列q[]为一个严格单调上升的序列
for(int i = 1; i <= n; i ++)
{
if(a[i] > q[len]) q[++ len] = a[i];
else
{
int L = 0, R = len, mid;
while(L < R)
{
mid = (L + R) / 2;
if(q[mid] >= a[i]) R = mid;
else L = mid + 1;
}
q[L] = min(q[L], a[i]);
}
}
cout << len << endl;
}