动态规划之最长上升子序列模型(二)

1010. 拦截导弹 - AcWing题库

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。

但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。

某天,雷达捕捉到敌国的导弹来袭。

由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式

共一行,输入导弹依次飞来的高度。

输出格式

第一行包含一个整数,表示最多能拦截的导弹数。

第二行包含一个整数,表示要拦截所有导弹最少要配备的系统数。

数据范围

雷达给出的高度数据是不大于 3000030000 的正整数,导弹数不超过 10001000。

输入样例:
389 207 155 300 299 170 158 65
输出样例:
6
2
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m,k;
int dp[N],a[N],g[N];
/*
对于第一问,显然是求最长不上升子序列的长度
对于第二问,可以考虑贪心的思路:
    从前往后扫描每个数,对每个数:
        情况1,如果当前所有子序列(已不升)的最后一个数(最小的)都小于当前数,则新开一个子序列
        情况2,如果存在大于等于的,则将当前数加到已有合法子序列中最后一个数最小的子序列后面
    贪心证明:
    1.如何证明两个数相等:A>=B,A<=B
    A表示贪心算法得到的个数,B表示最优解的个数
    显然B<=A,证A<=B,调整法
    假设A得到的方案与B不同,则必然存在一个数x,使得A的某个子序列与B不同
    A:.......a x.....  ,由贪心策略,x<=a
    B:.......b x,,,,,, ,由于A的贪心策略a是大于等于x的最小数,而B与A不同,则b>a
    将x后的序列交换,得到A‘,B',考虑这两个新序列是否合法
    A'.....a x,,,,, ,由a>=x,x后序列单调不升,合法
    B'.....b x..... ,由b>a>=x,x后序列单调不升,合法
    所以交换过后得到的两个子序列仍然合法,并不会增加方案数,所以贪心思路是正确的
    经过有限次调整,可以把B调整为A
    于是可以得到一个对偶问题,一个序列最少用多少个非上升子序列覆盖数量等于这个序列的最长上升子序列的个数
    
    用一个数组g[k]存所有单调不升子序列的最后一个数,显然g[k]数组是单调上升的,那么g数组的大小k就是最少需要配备的系统数
    从前往后遍历每一个数x,假设此时g已经存在了一些数,找到>=x的第一个数a,
    因为a是大于等于x的第一个数,所以存在数b和c,满足c<x<=a<b,可以将a替换为x,而不影响g数组的单调性
    所以可以不断替换得到g数组   
*/
void solve(){
    int x;
    int res=0;
    while(cin>>x)a[n++]=x;
    //最长不上升子序列个数
    /*
    for(int i=n-1;i>=0;i--){
        dp[i]=1;
        for(int j=n-1;j>i;j--){
            if(a[i]>=a[j])dp[i]=max(dp[i],dp[j]+1);
        }
        res=max(res,dp[i]);
    }
    */
    int cnt=0;//最长不上升子序列的个数
    for(int i=0;i<n;i++){
        int t=upper_bound(g,g+cnt,a[i],greater<int>())-g;
        if(t==cnt)g[cnt++]=a[i];
        else g[t]=a[i];
    }
    cout<<cnt<<endl;
    cnt=0;//导弹配备的系统数,即最长上升子序列的个数
    memset(g,0,sizeof g);
    /*
    dp解,小数据
    memset(dp,0,sizeof dp);
    res=0;
    for(int i=n;i>=1;i--){
        dp[i]=1;
        for(int j=n;j>i;j--){
            if(a[i]<a[j])dp[i]=max(dp[i],dp[j]+1);
        }
        res=max(res,dp[i]);
    }
    cout<<res<<endl;*/
    
    //贪心解,大数据
    for(int i=0;i<n;i++){
        /*
        int k=0;
        while(k<cnt&&g[k]<a[i])k++;//在g数组中找到第一个大于等于a[i]的数
        g[k]=a[i];//用更小的a[i]来替换
        if(k>=cnt)cnt++;
        */
        //由前面推理g数组具有单调性,所以可以用二分!
        int t=lower_bound(g,g+cnt,a[i])-g;//找到大于等于a[i]的第一个数
        if(t==cnt)g[cnt++]=a[i];//如果没找到,则新开一个
        else g[t]=a[i];//找到了就替换
    }
    cout<<cnt<<endl;
    return ;
}
signed main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int _;_=1;
    while(_--)solve();
    return 0;
}

187. 导弹防御系统 - AcWing题库

为了对抗附近恶意国家的威胁,R𝑅 国更新了他们的导弹防御系统。

一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。

例如,一套系统先后拦截了高度为 33 和高度为 44 的两发导弹,那么接下来该系统就只能拦截高度大于 44 的导弹。

给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。

输入格式

输入包含多组测试用例。

对于每个测试用例,第一行包含整数 n𝑛,表示来袭导弹数量。

第二行包含 n𝑛 个不同的整数,表示每个导弹的高度。

当输入测试用例 n=0𝑛=0 时,表示输入终止,且该用例无需处理。

输出格式

对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。

数据范围

1≤n≤501≤𝑛≤50

输入样例:
5
3 5 2 4 1
0 
输出样例:
2
样例解释

对于给出样例,最少需要两套防御系统。

一套击落高度为 3,43,4 的导弹,另一套击落高度为 5,2,15,2,1 的导弹。

#include<bits/stdc++.h>
using namespace std;
const int N=55;
int a[N],up[N],down[N],ans=100,n;
/*
思路:dfs每一个数,看把它放到最长上升子序列还是最长下降子序列
如果放在最长上升子序列,则枚举放在哪一个最长上升子序列
如果放在最长下降子序列,则枚举放在哪一个最长下降子序列

但是这样会导致时间复杂度过大,因为光是枚举每一个状态就有2^n次方种,而在每种状态中还要考虑在很多个最长上升(下降)子序列中放在哪一个
对于第二种枚举,我们可以考虑贪心优化,
    如果是把一个数放在最长上升子序列,那么怎样才能让上升的系统数量最小呢,显然是放在大于这个数的最长上升子序列最后一个数最大的后面,并替换这个数
    如果是把一个数放在最长下降子序列,那么怎样才能让下降的系统数量最小呢,显然是放在小于这个数的最长上升子序列最后一个数最大的后面,并替换这个数
*/
void dfs(int u,int v,int t){
    if(u+v>=ans)return;//剪枝,不是最小的了,没必要再搜    
    if(t==n){
        //搜到了第n个数
        if(u+v<ans)ans=u+v;
        return;
    }
    /*
    //用二分,nlogn速度还不如枚举。。。。
    //cout<<u<<' '<<v<<' '<<t<<endl;
    //dfs放在上升子序列
    int pos=upper_bound(up+1,up+u+1,a[t],greater<int>())-up;//找第一个末尾数小于a[t]的最大数
    int temp=up[pos];
    up[pos]=a[t];
    dfs(max(u,pos),v,t+1);
    up[pos]=temp;
    //dfs放在下降子序列
    int pos2=upper_bound(down+1,down+v+1,a[t])-down;//找第一个末尾数大于a[t]的数
    temp=down[pos2];
    down[pos2]=a[t];
    dfs(u,max(v,pos2),t+1);
    down[pos2]=temp;
    */

    int i;
    //cout<<u<<' '<<v<<' '<<t<<endl;
    //纯枚举
    for(i=1;i<=u;i++)if(up[i]<a[t])break;
    int temp=up[i];
    up[i]=a[t];
    dfs(max(u,i),v,t+1);
    up[i]=temp;
    
    for(i=1;i<=v;i++)if(down[i]>a[t])break;
    temp=down[i];
    down[i]=a[t];
    dfs(u,max(v,i),t+1);
    down[i]=temp;
}
void solve(){
    while(cin>>n,n){
        ans=100;
        for(int i=0;i<n;i++)cin>>a[i];
        dfs(0,0,0);
        cout<<ans<<endl;
    }
}
signed main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int _;_=1;
    while(_--)solve();
    return 0;
}

  • 18
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值