一、LIS+贪心
1、贪心策略
使用尽可能少的导弹拦截系统,拦截所有导弹:
-
当出现新的敌方导弹时,我方从已有的拦截系统中,选取高度最低且可以拦截该导弹的那套系统,进行拦截。
-
如果没有能拦截该导弹的拦截系统,则要新增一套拦截系统。
-
为每一套拦截系统维护着一个数字,描述此套拦截系统最后面的拦截高度是多少,也就是最低高度。
-
由下一套系统是因为当前数字比前面的数字发生上升而创建的,所以,它肯定比前面的大。
-
如果再来一个小的,加入到现在数据最小的系统后面,也是最节约成本的,防止大数浪费。这样下面,这个数组就成了单调上升的数组了。
2、实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n; //n个导弹
int a[N]; //记录导弹飞行高度
int f[N]; //以f[i]为结尾的连续上升子序列的个数
int g[N]; //多套导弹防御系统的最后一个高度值(最矮的那个), 只需要记录最矮的即可。
int main() {
//第一问
while (cin >> a[n]) n++;//下标从0开始啊~
//正常的LIS求最长不上升子序列
int res = 0;
for (int i = 0; i < n; i++) {
f[i] = 1;
for (int j = 0; j < i; j++)
if (a[i] <= a[j])f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
printf("%d\n", res);
//第二问,求用几套导弹拦截系统可以完成拦截任务
int cnt = 0;//使用的导弹拦截系统套数
//遍历每个导弹
for (int i = 0; i < n; i++) {
//此导弹使用第几组导弹防御系统进行拦截?从前往后找的序列
int k;
//搜索,找到第一个能装下a[i]的序列
for (k = 0; k < cnt; k++)
if (g[k] >= a[i]) break;
//如果没有任何一个序列存在大于等于当前数a[i]的,则需要创建一个新的序列
if (k == cnt) cnt++;
//不管是否创建新的序列,都将a[i]放到此防御系统中
g[k] = a[i];
}
//输出
printf("%d\n", cnt);
return 0;
}
4、二分法优化版本
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n; //n个导弹
int a[N]; //记录导弹飞行高度
int f[N]; //以f[i]为结尾的连续上升子序列的个数
int g[N]; //多套导弹防御系统的最后一个高度值(最矮的那个), 只需要记录最矮的即可。
int main() {
//第一问
while (cin >> a[n]) n++;//下标从0开始啊~
//正常的LIS求最长不上升子序列
int res = 0;
for (int i = 0; i < n; i++) {
f[i] = 1;
for (int j = 0; j < i; j++)
if (a[i] <= a[j])f[i] = max(f[i], f[j] + 1);
res = max(res, f[i]);
}
printf("%d\n", res);
//数组g的每个元素代表一套导弹拦截系统的拦截序列
//g[i]表示此时第i套导弹拦截系统所拦截的最后一个导弹的高度
int cnt = 0;
for (int i = 0; i < n; i++) { //遍历所有的导弹高度
int k = lower_bound(g, g + cnt, a[i]) - g;
if (k == cnt) g[cnt++] = a[i]; //a[i]开创一套新拦截系统
else g[k] = a[i]; //a[i]成为第p套拦截系统最后一个导弹高度
}
//输出
printf("%d\n", cnt);
return 0;
}
二、Dilworth定理解法
1、狄尔沃斯定理
最长上升子序列的长度\((lis)\)就是能构成的不上升序列的个数
有了上面的数学定理神器,我们就可解决掉本题了:
-
最多拦截多少个导弹
因为拦截系统第一下是可以任意高的,以后只能拦截比它矮的,所以,这是一个最长下降子序列,也就是\(f[i]\)只能由它前面比它高的\(j\)转化而来。 -
需要多少套拦截系统
这个问题如果没有上面的数学定理,其实不好回答。有了上面的定理,其实问题转化为求最长上升子序列的长度就是答案。
2、实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n; //导弹数量
int a[N]; //导弹高度
int f[N]; //以i结尾的最长下降子序列长度
int g[N]; //以i结尾的最长上升子序列长度
int ans1; //最长下降子序列的长度 max(f[i])
int ans2; //最长上升子序列的长度 max(g[i])
/*
一旦遇到上升的导弹就一定会用第二套导弹系统。没有其他方法能将上升序列的两个导弹一起打下来。
所以此题也就可以转化到最长下降子序列和最长上升子序列。
*/
int main() {
while (cin >> a[n]) n++;
for (int i = 0; i < n; i++) {
f[i] = 1;
g[i] = 1;
for (int j = 0; j < i; j++) {
if (a[i] <= a[j]) f[i] = max(f[i], f[j] + 1);//最长下降子序列
else g[i] = max(g[i], g[j] + 1);//最长上升子序列
}
ans1 = max(ans1, f[i]);
ans2 = max(ans2, g[i]);
}
//输出
printf("%d\n", ans1);
printf("%d\n", ans2);
return 0;
}