动态规划——最长单调不升子序列和最长单调上升序列(洛谷P1020:导弹拦截)

本文介绍了一种导弹拦截系统的算法实现,采用动态规划与树状数组优化,解决了如何计算最多能拦截多少导弹的问题,并给出了完整的代码实现。

1.题目描述

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

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

输入输出格式
输入格式:
11行,若干个整数(个数 \le 100000≤100000)

输出格式:
22行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入输出样例
输入样例#1: 复制
389 207 155 300 299 170 158 65
输出样例#1: 复制
6
2
说明
为了让大家更好地测试n方算法,本题开启spj,n方100分,nlogn200分

每点两问,按问给分

2.动态规划解决问题

我们先来看一个数组
1 2 5 3 4
我们先遍历数组个数,前i个的规律,设D(i) 以第i个数字为子序列最大值的最长单调上升序列个数
前1个 d(1)=max{1(如果就它本身就是1)}=1
前2个 因为2>1 于是有2个选项 d(2)=max{d(1)+1,1}=max{1,2}
前3个 因为 5>2,5>1 于是有3个选项 d(3)=max{d(1)+1,d(2)+1,1}=max{3,2,1}=3
前4个 因为 3>1,3>2 还是有3个选项 d(4)=max{d(1)+1,d(2)+1,1}=max{3,2,1}=3
前5个 因为 4>1,4>2,4>3 于是有4个选项d(4)=max{d(1)+1,d(2)+1,d(4)+1}=max{3,2,1}=4
所以答案是4

#include <iostream>
#include <cstdio>
using namespace std;
int a[100001]; 
int f[100001]={0};
int main(int argc, char *argv[])
{
    int n=0,ans=0;
    while(scanf("%d",&a[n++])!=EOF);
    
    //for(int i=0;i<n;i++)
    //	printf("%d ",a[i]);
    n=n-1;
    
    for(int i=0;i<n;i++){//求最长单调不升子序列
        f[i]=1;//如果子序列中只有a[i],则长度为1
        for(int j=0;j<i;j++){//查看a[i]前面有多少个数字比它小
            if(a[j]>=a[i]&&f[i]<(f[j]+1))//如果a[j]>=a[i],说明a[i]可以放在a[j]后面
                f[i]=f[j]+1;//取更大值
        }
        if(f[i]>ans) ans=f[i];//取最大值
    }
    printf("%d\n",ans);
    for(int i=0;i<n;i++){
        f[i]=0;
    }
    ans=0;
    //同理
    for(int i=0;i<n;i++){
        f[i]=1;
        for(int j=0;j<i;j++){
            if(a[j]<a[i]&&f[i]<(f[j]+1))
                f[i]=f[j]+1;
        }
        if(f[i]>ans) ans=f[i];
    }
    printf("%d\n",ans);
    return 0;
}

3.使用树状数组来优化复杂度(从O(n^2)到O(nlogn))

我们发现可以使用树状数组来减少我们的复杂度。
因为树状数组不需要每一个都遍历,它的查询和改变复杂度是O(logn)
所以我们开始学习一下树状数组吧

3.1树状数组的基本知识

此内容有部分转载https://blog.csdn.net/bestsort/article/details/80796531文章
在这里插入图片描述
A是普通数组
C是树状数组

如图可以知道

C[1] = C[0001] = A[1];

C[2] = C[0010] = A[1]+A[2];

C[3] = C[0011] = A[3];

C[4] = C[0100] = A[1]+A[2]+A[3]+A[4];

C[5] = C[0101] = A[5];

C[6] = C[0110] = A[5]+A[6];

C[7] = C[0111] = A[7];

C[8] = C[1000] = A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];

所以比如C[8] 就可以直接统计A[1]~A[8]

除了求和我们还可以统计最大值

3.2 lowbit函数

int lowbit(int x){
	return x&(-x);
}

它可以求出一个数字转换为二进制后,取出最低位的1;

lowbit(1)=1 lowbit(2)=2 lowbit(3)=1 lowbit(4)=4 lowbit(5)=1 lowbit(6)=2 lowbit(7)=1 lowbit(8)=8 lowbit(9)=1 lowbit(10)=2 lowbit(11)=1 lowbit(12)=4 lowbit(13)=1 lowbit(14)=2 lowbit(15)=1 lowbit(16)=16
举个例子lowbit(6D) = lowbit(0110B) =10B=2D

所以如果 一个数i 不断遍历 i=i-lowbit(i) ,那么就可以直接找到比它小的数的最大值。比i=i-1快一大截
改变自身的最大值也可以是i=i+lowbit(i)遍历
(仔细研究一下树状数组,它就是这个性质的!!可以节约复杂度)

最终代码

所以升级后的代码为

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int a[100001]; 
int f[100001]={0};
int n=0,ans=0,big=0;
int lowbit(int x){
	return x&(-x);
}
int query1(int x){//查询比x值大的数 的最长单调不升子序列
	int mmax=0;
	for(int i=x;i<=big;i+=lowbit(i))
		if(f[i]>mmax) mmax=f[i];
	return mmax;
}
void add1(int x,int v){//改变最长单调不升子序列
	for(int i=x;i>0;i-=lowbit(i))
		if(v>f[i]) f[i]=v;
}
int query2(int x){//查询比x值小的数 的最长单调上升子序列
	int mmax=0;
	for(int i=x;i>0;i-=lowbit(i))
		if(f[i]>mmax) mmax=f[i];
	return mmax;
}
void add2(int x,int v){//改变最长最长单调上升子序列
	for(int i=x;i<=big;i+=lowbit(i))
		if(v>f[i]) f[i]=v;
}
int main(int argc, char *argv[])
{
	
	while(scanf("%d",&a[++n])!=EOF){
		big = max(big,a[n]);
	};
	
	n=n-1;

	for(int i=1;i<=n;i++){
		int mmax = query1(a[i])+1; //当前以a[i]为最大值的最大单调下降子序列个数
		add1(a[i],mmax);
		ans=max(ans,mmax); 
	}
	printf("%d\n",ans);
	
	
	ans=0;
	memset(f,0,sizeof(f));
	for(int i=1;i<=n;i++){
		int mmax = query2(a[i])+1; //当前以a[i]为最大值的最大单调上升子序列个数
		add2(a[i]+1,mmax);
		ans=max(ans,mmax); 
	}
	printf("%d\n",ans);
/*
	for(int i=0;i<n;i++){
		f[i]=0;
	}
	ans=0;
	for(int i=0;i<n;i++){
		f[i]=1;
		for(int j=0;j<i;j++){
			if(a[j]<a[i]&&f[i]<(f[j]+1))
				f[i]=f[j]+1;
		}
		if(f[i]>ans) ans=f[i];
	}
	printf("%d\n",ans);*/
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值