这里以一道经典的题目为例
题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度,计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入格式
一行,若干个整数,中间由空格隔开。
输出格式
两行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
问题一:
经典的动态规划问题,在给出的导弹高度序列中选取最长的不上升子序列,并输出长度
vector<int> m(100010);
int len=0;
m[0]=1e9;
for (int i=1;i<n;i++){
if (a[i]<=m[len]){
m[++len]=a[i];
}else{
auto iter=lower_bound(m.begin(),m.begin()+len+1,a[i],[](int x,int y){return x>=y;});
*iter=a[i];
}
}
cout<<len<<endl;
采用记录序列二分查找的方式优化时间复杂度至 $O(n\log n)$
注意的,lower_bound
默认为查找大于等于参数 $k$ 的位置,范围该位置的迭代器
不同于最长上升子序列,这道题目中需要记录最长不上升子序列,所以 lower_bound
应查找小于等于参数 $k$ 的位置
这里采用 lambda 表达式,匿名定义 cmp
函数
auto iter=lower_bound(m.begin(),m.begin()+len+1,a[i],[](int x,int y){return x>=y;});
等效于
bool cmp(int x,int y){
return x>=y;
}
auto iter=lower_bound(m.begin(),m.begin()+len+1,a[i],cmp);
问题二:
翻译过来的意思为在给定的导弹高度序列中,求出覆盖完所有元素的不上升子序列的最少个数
可以采用最小链覆盖问题的模型,Dilworth定理来解决
该定理断言:对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目。此定理的对偶形式亦真,它断言:对于任意有限偏序集,其最长链中元素的数目必等于其最小反链划分中反链的数目,由偏序集 $P$ 按如下方式产生的图 $G$ 称为偏序集的可比图:$G$ 的节点集由 $P$ 的元素组成,而 $e$ 为 $G$ 中的边,仅当 $e$ 的两端点在 $P$ 中是可比较的,有限全序集的可比图为完全图——*百度百科
因为涉及到组合数学与离散数学基础,这里不详尽解释该定理,也不会过多地验证它的正确性
对于这道题目而言,这个定理给出了一个重要的结论:
覆盖完所有元素的不上升子序列的最少个数(最小链覆盖)即为最长上升子序列的长度
vector<int> dm(100010);
len=0;
dm[0]=0;
for (int i=1;i<n;i++){
if (a[i]>dm[len]){
dm[++len]=a[i];
}else{
auto iter=lower_bound(dm.begin(),dm.begin()+len+1,a[i],[](int x,int y){return x<y;});
*iter=a[i];
}
}
cout<<len<<endl;
评论区(暂无评论)