HDU1257 详细分析,多种解法(全网最全)

Problem Description
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高度.某天,雷达捕捉到敌国的导弹来袭.由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹.
怎么办呢?多搞几套系统呗!你说说倒蛮容易,成本呢?成本是个大问题啊.所以俺就到这里来求救了,请帮助计算一下最少需要多少套拦截系统.

Input
输入若干组数据.每组数据包括:导弹总个数(正整数),导弹依此飞来的高度(雷达给出的高度数据是不大于30000的正整数,用空格分隔)

Output
对应每组数据输出拦截所有导弹最少要配备多少套这种导弹拦截系统.

Sample Input
8 389 207 155 300 299 170 158 65

Sample Output
2
http://acm.hdu.edu.cn/showproblem.php?pid=1257

这道题目非常经典,几乎ACM入门必做,因此我决定将所有常用的做法汇总到一起进行梳理与分析,同时这道题出的特别有代表性,几乎囊括了大多数ACM题目的解法
我知道这篇字很多,但我希望你能够看完,因为真的很有帮助。。。

首先看题目可以提炼到两个重要的信息:
1.每次发射的炮弹高度任意
2.发射的炮弹在一次拦截后就会无法再次拦截更高的导弹,换句话说可以将系统发射的炮弹视为一条线,每次拦截这条线低的导弹后,这条线也会降低至与拦截的导弹相同的高度

因此,问题可以看作画线,怎么画线能使得线的数量最少并且拦截所有导弹
方法一:模拟+暴力
第一种方法营运而生,我认为它是一种模拟的思想,因为所有导弹都要拦截所以第一发导弹是必须拦截的,同时你会发现无论它的初始高度定的多高都会被拉低到第一发导弹的高度。。。
因此一种朴素的想法诞生了,既然是这样那每来一发新的导弹就去系统数组里去寻找是否有系统的拦截范围在这之上,这时分为两种情况
1.如果有的话,即dp[j]>height[i]那么说明无需新增系统,只需要降低某一个已存在系统的拦截线即可,那么应该选哪个呢 很容易想到应该使用贪心的思想,去寻找局部问题的最优解,将每个系统的拦截线与当前的导弹高度对比,选择差最小的那个,通俗一点讲就是这样可以确保每次都选择了相对导弹高度最低的系统,并保存了其他较高的系统,避免浪费
2.没有,很简单 如果没有的话就再(整)一个新的系统,用于拦截当前导弹

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=100005;
int n,height[maxn];//储存导弹的高度
int dp[maxn];//储存每个系统的拦截线(即当前的最高高度)
int i,j,ans,pjudge,njudge,len,flag,t;
int solve(int n)
{
    if(n<0) n=-n;
    return n;
}

int main()
{
    while(~scanf("%d",&n))
    {
        ans=1;
        memset(dp,0,sizeof(dp));
        for(i=1;i<=n;i++) scanf("%d",&height[i]);
        len=1;
        dp[1]=height[1];

        for(i=2;i<=n;i++)
        {
            flag=0;
            pjudge=50000;
            for(j=1;j<=len;j++)
            {
                if(dp[j]>height[i])
                {
                    njudge=dp[j]-height[i];
                    if(njudge<pjudge)
                    {
                        pjudge=njudge;
                        t=j;
                        flag=1;
                    }
                }
            }
            if(flag==0)
            {
                dp[len+1]=height[i];
                len++;
            }
            else
            {
                dp[t]=height[i];
            }
        }

        printf("%d\n",len);
    }
	return 0;
}

好了,基础部分已经看完,现在是进阶篇。。。
当前的导弹系统通过观察可以发现一个问题,那就是每个系统能拦截的都是一组降序数列
比如在案例中:
1号系统:389 207 155
2号系统:300 299 170 158 65
答案为2
不要小看这一点,这对之后的问题很有帮助
现在问题转化为有多少个降序序列
与此同时可以反向思考是什么导致了降序序列的断裂,个人理解也就是某个较大的数字打断了降序序列,所以可将每个这样的数看作一个降序序列的终结,同时也是一个新的系统的开始,因此问题最终转换为寻找最长递增子序列的长度 (因为每一个递增序列都代表着一套新的系统)
样例中应为:155 300 答案为2

方法二:利用序列的性质
定义一个数组d[],用它的长度来记录答案
从头到尾遍历高度,并且判断当high[]大于d[]末尾的数字,则将其加入d[],并且再d的长度len上+1
当小于时,去d中找到第一个大于它的数字并替换
其中替换操作原理为帮助后面的挑出更有希望的因为越小的数就有更大的可能构成更长的单调递增序列,同时也有可能替换末尾的数字对之后的序列造成影响

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=20000;
int height[maxn],n,i,j;
int solve()
{
    int i,j;
    int len=1;
    int dp[maxn];
    dp[1]=height[1];
    for(i=2;i<=n;i++)
    {
        if(height[i]>dp[len]) dp[++len]=height[i];
        else
        {
            int j=lower_bound(dp+1,dp+1+len,height[i])-dp;
            dp[j]=height[i];
        }
    }
    return len;
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(i=1;i<=n;i++) scanf("%d",&height[i]);
        printf("%d\n",solve());
    }
	return 0;
}

方法三:动态规划
没错这道题还可以用动态规划来做!太神奇了。。。
dp[i]代表以第i个数结尾的最长递增子序列的长度

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=20000;
int height[maxn],n,i,j;
int solve()
{
    int i,j;
    int ans=1;
    int dp[maxn];
    dp[1]=1;
    for(i=2;i<=n;i++)
    {
        int mx=0;
        for(j=1;j<i;j++)
        {
            if(dp[j]>mx&&height[j]<height[i]) mx=dp[j];
            dp[i]=mx+1;
        }
        if(dp[i]>ans) ans=dp[i];
    }

    return ans;
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(i=1;i<=n;i++) scanf("%d",&height[i]);
        printf("%d\n",solve());
    }
	return 0;
}

方法四:今天学了点东西。。。有了新的想法
用到了最长公共子序列因为本题寻找的是最长递增子序列
那么我们可以先将这个序列升序排列,之后再拿原序列与它比较相同的数字
也就是求原来的序列与升序排序后两个序列的公共序列
公共序列也可以用动态规划解决

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<sstream>
#include<vector>
using namespace std;
int dp[1005][1005];
int a[1005],b[1005];
int main()
{
    int n,i,j;
    while(~scanf("%d",&n))
    {
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            b[i]=a[i];
        }

        sort(b+1,b+1+n);
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=n;j++)
            {
                if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1;
                else dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
            }
        }

        printf("%d\n",dp[n][n]);
    }
	return 0;
}

好了,看到这里希望你已经学会了这道题。。。
写了这么多,希望有网友给个评论啥的,总感觉自己在玩单机。。。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值