[线性动态规划][最长上升/下降序列][P1020 导弹拦截]线性动态规划做题理解和总结

原题目链接:导弹拦截

题目描述

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

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

输入输出格式

输入格式:

1行,若干个整数(个数≤100000)

输出格式:

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

O(N^2)方法: 

由题意可知,第一个题目是问最长不上升子序列,第二问求最长上升子序列(Dilworth定理)。

利用动态规划的思想,两个循环求序列长度就是了。

#include 
#include 
#include 

using namespace std;

int Height[100001];
int DP[100001];
int DP2[100001];

int main(void)
{
    int Tmp;
    int Index = 1;
    int ans1 = 0;
    int ans2 = 0;
    
    while(scanf("%d",&Tmp) != EOF)
    {
        Height[Index] = Tmp;
        Index++;
    }
    
    for(int i = Index;i > 0;i--)
    {
        for(int j = 1;j <= Index;j++)
        {
            if(i < j && Height[i] >= Height[j])
            {
                DP[i] = max(DP[i],DP[j]+1);
            }
        }
        ans1 = max(ans1,DP[i]);
    }
    
    for(int i = 1;i < Index;i++)
    {
        DP2[i] = 1;
        
        for(int j = 1;j < i;j++)
        {
            if(Height[j] < Height[i])
            {
                DP2[i] = max(DP2[i],DP2[j]+1);
            }
        }
        ans2 = max(ans2,DP2[i]);
    }
    cout << ans1 << endl;
    cout << ans2 << endl; 
    return 0;
}

O(nlogn)的方法:

(树状数组代码短小精悍,其基本用法就是求一个数组某区间的和,访问和修改时间复杂度为O(nlogn))

树状数组学习文章

利用树状数组的方法,将导弹高度作为树状数组访问下标,通过倒序遍历和正序遍历的方法求最长序列。

可以自己手动模拟一下,加深理解:

#include 
#include 

using namespace std;

int Add(int,int);
int Query(int);
int lowbit(int);

int TreeDp[50001];
int Height[100001];
int N;
int MaxN;

int main(void)
{
    memset(TreeDp,0,sizeof(TreeDp));
    int ans1 = 0,ans2 = 0;
    int index = 1;
    while(cin >> Height[index])
    {
        MaxN = max(MaxN,Height[index]);
        index++;
    }
    index--;
    //遍历顺序很重要,牵涉到序列的排布顺序
    for(int i = index;i > 0;i--)
    {
        //最长不上升序列
        int MaxAns = Query(Height[i])+1;
        Add(Height[i],MaxAns);
        ans1 = max(ans1,MaxAns);
    }
    memset(TreeDp,0,sizeof(TreeDp));
    
    //这里的遍历顺序在树状数组中DP求最长升至关重要,上同,可以手动模拟一下
    for(int i= 1;i<=index;i++)
    {
        //这里减去一是为了防止出现后面与所求高度相同的高度,否则这样就不是最长升序列了
        int MaxAns = Query(Height[i]-1)+1;
        Add(Height[i],MaxAns);
        ans2 = max(ans2,MaxAns);
    }
    cout << ans1 << "\n" << ans2 << endl;
    return 0;
}

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

int Add(int x,int y)
{
    for(int i = x;i <= MaxN;i+=lowbit(i))
    {
        TreeDp[i] = max(TreeDp[i],y);
    }
    return 0;
}

int Query(int x)
{
    int ans = 0;
    for(int i = x;i > 0;i-=lowbit(i))
    {
        ans = max(TreeDp[i],ans);
    }
    return ans;
}

 

你可能感兴趣的:(算法,NOIP,C++,树状数组)