题目链接:信息学奥赛一本通(C++版)在线评测系统
对于本题的第一问就是简单的求最长不下降子序列,就不赘述,主要讲解第二问需要配备的系统数。
主要思想:
贪心法:每颗导弹来袭之前,使能拦截这颗导弹系统中的上一次拦截导弹高度最低的那一套来拦截。若不符合存在这一条件的系统,则使用一套新系统。
难点:验证这个贪心法是正确的。
这个问题等价于需要多少个下降子序列才能把整个序列覆盖完。我们分步来讲解
第一步:
第二步
我们将流程整理一遍贪心的流程:
第三步:证明最优解就是贪心法
第四步:如何去实现它
首先,我们可以建立一个g[ ]数组来记录所有已经开好了的序列的结尾的数。然后每一次从g数组中找到大于等于自己最小的数。我们会发现g数组一定是单调的,单调递增的,这个看你怎么看,正着看序列还是反着看。我们认为是单调递增的。
- 如果g数组是空的,那么一定单调
- 如果不空,我们找到大于当前X的最小的数替换,任然是单调递增的。
一个序列最少用多少个非下降子序列把整个序列覆盖掉的方案数 等于 最长上升子序列的方案数
对偶问题,Dilworth定理
代码
#include<bits/stdc++.h> // 包含标准库和C++扩展库
using namespace std; // 使用标准命名空间
const int N = 1010; // 定义一个常量N,作为数组的最大大小
int n; // 声明变量n,用于存储数组的长度
int a[N]; // 声明一个大小为N的数组a,用于存储输入的整数序列
int dp[N], g[N]; // 声明两个大小为N的数组dp和g,用于存储算法过程中的中间结果
int main(){
while(cin>>a[n])n++; // 循环读取输入直到文件结束,并将读取的数存入数组a,同时计算数组长度n
int maxx = -1; // 初始化maxx为-1,用于存储最长不下降子序列的最大长度
for(int i=1; i<=n; i++){ // 外层循环,从1到n
dp[i] = 1; // 初始化dp[i]为1,表示以第i个元素结尾的最长不下降子序列至少包含这一个元素
for(int j=1; j<i; j++){ // 内层循环,从1到i-1
if(a[j] <= a[i]){ // 如果第j个元素小于等于第i个元素
dp[i] = max(dp[i], dp[j] + 1); // 更新dp[i]为max(dp[i], dp[j] + 1),即以i结尾的最长不下降子序列长度
maxx = max(maxx, dp[i]); // 更新maxx为dp[i]和当前maxx中的较大值
}
}
}
// for(int i =1 ;i<=n;i++)cout<<dp[i]<<" ";
// cout<<endl;
cout<<maxx<<endl; // 输出最长不下降子序列的长度
int cnt = 1; // 初始化cnt为1,用于记录最长递增子序列的个数
for(int i = 1; i <= n; i++){
int k = 1; // 初始化k为1,用于记录当前扫描到的序列
while(k<=cnt && g[k] < a[i]) k++; // 当k小于等于cnt且g[k]小于a[i]时,k++
g[k] = a[i]; // 将a[i]赋值给g[k],更新当前扫描到的序列的最后一个元素
if(k > cnt)cnt++; // 如果k大于cnt,说明找到了一个新的最长递增子序列,cnt++
}
cout<<cnt-1<<endl; // 输出最长递增子序列的个数,减1是因为cnt初始为1
}