今日熟悉了贪心的上升下降子序列长度的优化解法以及和dfs的结合
以及LCS朴素写法
Acwing 287.导弹防御系统
为了对抗附近恶意国家的威胁,R𝑅 国更新了他们的导弹防御系统。
一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。
例如,一套系统先后拦截了高度为 33 和高度为 44 的两发导弹,那么接下来该系统就只能拦截高度大于 44 的导弹。
给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。
输入格式
输入包含多组测试用例。
对于每个测试用例,第一行包含整数 n𝑛,表示来袭导弹数量。
第二行包含 n𝑛 个不同的整数,表示每个导弹的高度。
当输入测试用例 n=0𝑛=0 时,表示输入终止,且该用例无需处理。
输出格式
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。
数据范围
1≤n≤50
输入样例:
5
3 5 2 4 1
0
输出样例:
2
思路:
首先看到严格单调递增和严格单调下降就想到最长上升和下降子序列
本题可以选择上升子序列和下降子序列,但每次不知道选择哪一个,所以只能进行暴力搜索来穷举最优解,使用全局变量ans来记录最小方案并更新
对于上升子序列,使用贪心的方法,可以用一个单调数组来维护结尾的序列值以及子序列的长度,每次做的操作就是将该数加入到该单调数组中,但该单调数组并不是最终的最长上升子序列
只是长度与之相等,因此我们遍历这个序列每次将用这个数来更新单调数组,若在尾部加入则单调数组长度+1,若在内部加入则将该值替换单不改变单调性
对于最长上升子序列则是每次将加入的数放入结尾数最大的且小于加入的数那个,因为如果加入到结尾最小的那个后面就会导致不是最优解
对于最长下降子序列则是每次将加入的数放入结尾数最小的且大于加入数的那个
最后暴力枚举即可
#include <iostream>
#include <algorithm>
#define endl '\n'
using namespace std;
const int N=51;
int a[N],n,ans;
int up[N],down[N];//上升序列结尾数,下降序列结尾数
void dfs(int u,int s,int x){//当前枚举到第几个数,上升子序列个数,下降子序列个数
if(s+x>=ans) return;
if(u==n){
ans=s+x;
return;
}
//开始搜索上升子序列个数和下降子序列个数
int k=0;
while(k<s&&up[k]>=a[u]) k++;
int t=up[k];//保存状态
up[k]=a[u];//添加
if(k<s) dfs(u+1,s,x);//如果新数被合并了
else dfs(u+1,s+1,x);//如果新加了一个
up[k]=t;//恢复
k=0;
while(k<x&&down[k]<=a[u]) k++;
t=down[k];
down[k]=a[u];
if(k<x) dfs(u+1,s,x);
else dfs(u+1,s,x+1);
down[k]=t;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
while(cin>>n,n){
for(int i=0;i<n;i++) cin>>a[i];//读入
ans=n;
dfs(0,0,0);
cout<<ans<<endl;
}
return 0;
}
最长公共子序列朴素写法
f[i][j]为包含a前i个数,b前j个数的子序列长度,属性为最大值
f[i-1][j-1]包含于f[i-1][j]和f[i][j-1],所以取最值一般省略
选第i个不选第j个包含于f[i][j-1],另外一个同理,再和最后一个i,j都选f[i-1][j-1]+1取最值即可
由于第i,j个不一定相等要进行特判
#include <iostream>
using namespace std;
const int N=1e3+7;
int f[N][N],n;
int main(){
cin>>n;
string s1,s2;
cin>>s1>>s2;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
f[i][j]=max(f[i-1][j],f[i][j-1]);
if(s1[i-1]==s2[j-1]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
cout<<f[n][n];
return 0;
}