zoukankan      html  css  js  c++  java
  • [Noip1999]导弹拦截 剖析

    题目链接

    拦截导弹(Noip1999)

    某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
    输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
    

    INPUT

    389 207 155 300 299 170 158 65

    OUTPUT

    6(最多能拦截的导弹数)
    2(要拦截所有导弹最少要配备的系统数)

    这是一道很经典的题目,做法也是多种多样的。
    首先,第一问 O(n^2) 的方法是很容易想到的,就是求最长不升子序列的长度~
    有:

    $$ f[i]=max(f[i],f[j]+1)(a[i]<=a[j] && i>j) $$

    但在看第二问之前,我们先需要知道Dilworth定理。它的内容大致是:

    对于一个偏序集,最少链划分等于最长反链长度。

    这句话很拗口,是吧?翻译成题目意思就是说,不上升子序列的个数至少为最长上升子序列的长度(如果不理解,可以参考这位大牛的博客)
    即:

    $$ g[i]=max(g[i],g[j]+1)(a[i]>a[j] && i>j) $$

    下贴代码:

    #include<bits/stdc++.h>
    #define mod (int)(1e9+7)
    using namespace std;
    const int maxn=1010;
    typedef long long ll;
    const int inf=1e9;
    
    int up[maxn],down[maxn],a[maxn];
    int max1,max2;
    
    int main( ){
        int m,n,j,k,i,cnt=0;
        while(scanf("%d",&a[++cnt])!=EOF);cnt--;
        for(i=0;i<=cnt;i++)
        	up[i]=down[i]=1;
        for(i=2;i<=cnt;i++){
            for(j=1;j<i;j++){
                if(a[i]<=a[j] && up[i]<up[j]+1)
                    max1=max(max1,(up[i]=up[j]+1));
            }
        }
        for(i=2;i<=cnt;i++){
            for(j=1;j<i;j++){
                if(a[j]<a[i] && down[j]+1>down[i])
                    max2=max(max2,(down[i]=down[j]+1));
            }
        }
        printf("%d
    %d
    ",max1,max2);
        return 0;
    }
    

    当然,我们只讲这么一点肯定不够是吧
    如果某个丧心病狂的出题人把导弹个数出成100000个,那我们的 O(n^2) Dp肯定就过不去了
    这时候我们发现只有 O(n log n) 与 O(n) 做法才能A掉。而显然后者是不太现实的,于是我们考虑用一个队列(或栈)来二分维护序列的长度
    比如做第一问的时候,可以先建立一个空队列,插入数的时候,只有两种情况:
    1.如果这个数比队列末尾的数还小,则直接将其入队
    2.另外的,就找到第一个比这个数小的数,并将其替换为这个数,因为它更具有潜力(这个过程借助二分实现)
    下贴代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    bool read(int& k){
        int Base=1;char Ch=getchar();
        while(!isdigit(Ch)){if(Ch=='-')Base=-1;if(Ch==EOF)return false;Ch=getchar();}
        while(isdigit(Ch)){k=(k<<1)+(k<<3)+(Ch^'0');Ch=getchar();}
        return true;
    }
    
    const int maxn=300010;
    int a[maxn],qx[maxn],qy[maxn];
    
    int main(){
        int i,j,k,m,n=0;
        while(read(a[++n]));n--; 
        int lenx=0,leny=0;
        qx[0]=1e9;
        for(i=1;i<=n;i++){
            if(a[i]<=qx[lenx]){qx[++lenx]=a[i];continue;}
            int head=1,tail=lenx;
            while(head<tail){
                int mid=((head+tail)>>1);
                if(a[i]<=qx[mid])head=mid+1;
                else tail=mid;
            }
            qx[head]=a[i];
        }
        qy[0]=-1e9;
        for(i=1;i<=n;i++){    
            if(a[i]>qy[leny]){qy[++leny]=a[i];continue;}
            int head=1,tail=leny;
            while(head<tail){
                int mid=(head+tail)>>1;
                if(a[i]>qy[mid])head=mid+1;
                else tail=mid;
            }
            qy[head]=a[i];
        }
        printf("%d
    %d
    ",lenx,leny);
        return 0;
    }
    

    个人喜欢手打队列,不过其实stl的lowerbound也是可以的(主要是不会啊QAQ)
    好吧,其实第二问还有一个奇妙的做法,但不知道二分图的孩子们可以先略过了
    上代码

    #include<bits/stdc++.h>
    #define mod (int)(1e9+7)
    #define mscheck(s) if(s=='-')Base=-1;else if(s==EOF)return false;
    using namespace std;
    typedef long long ll;
    const int inf=1e9;
    
    const int maxn=1010;
    int a[maxn],f[maxn],p[maxn],ansx,ansy;
    int beg[maxn*maxn*2],nex[maxn*maxn*2],to[maxn*maxn*2],e;
    int vis[maxn],link[maxn],ans,now;
    
    bool read(int& Value){
        int Base=1;char Ch=getchar();
        for(;!isdigit(Ch);Ch=getchar())mscheck(Ch);
        for(;isdigit(Ch);Ch=getchar())Value=Value*10+(Ch^'0');
        Value*=Base;return true;
    }
    
    void add(int x,int y){
        to[++e]=y;
        nex[e]=beg[x];
        beg[x]=e;
    }
    
    bool dfs(int x){
        for(int i=beg[x];i;i=nex[i]){
            int y=to[i];
            if(vis[y]!=now){
                vis[y]=now;
                if(!link[y] || dfs(link[y])){
                    link[y]=x;
                    return true;
                }
            }
        }
        return false;
    }
    
    int main( ){
        int m,n=0,j,k,i;
        while(read(a[++n]));n--;
        for(i=1;i<=n;i++){
            for(j=1;j<i;j++){
                if(a[j]>=a[i]){
                    f[i]=max(f[i],f[j]+1);
                    ansx=max(ansx,f[i]);
                    add(j,i);
                }
            }
        }
        for(i=1;i<=n;i++){
            now++;
            ansy+=dfs(i);
        }
        printf("%d %d",ansx+1,n-ansy);
        return 0;
    }
    

    这里第一问的做法与 O(n^2) 的没有区别,但看第二问之前我们先看一下二分图的一个重要定理:

    DAG图的最小路径覆盖数=节点数-二分图的最大匹配数

    大意就是如果要用最少的简单边覆盖整个图,那么这些边的个数就是节点数-二分图最大匹配数
    这个其实很好理解,首先我们建立一个二分图,满足A集合和B集合之间没有边,则定理显然成立。
    这时候,我们找到两个点ai,bi并连接一条匹配边(ai,bi),则路径覆盖数就比原先少了一个
    这时继续推广,不难发现每增加一条A、B间的匹配边,路径覆盖数就会减少一个;匹配边数不能再增加的时候,路径覆盖数正好为一。
    也就是说二分图的每一条匹配边,都与该二分图的一条DAG图的覆盖路径对应

    所以我们可以构造二分图,满足:
    如果

    $$ a[j]<a[i] && j<i 就连上一条边 <j,i> $$

    最后求一求导弹数-二分图匹配数就可以了

    最后再看一下一个类似题目

    题目链接

    [usaco]低价购买

    Description

    “低价购买”这条建议是在奶牛股票市场取得成功的一半规则。要想被认为是伟大的投资者,你必须遵循以下的问题建议:“低价购买;再低价购买”。每次你购买一支股票,你必须用低于你上次购买它的价格购买它。买的次数越多越好!你的目标是在遵循以上建议的前提下,求你最多能购买股票的次数。你将被给出一段时间内一支股票每天的出售价(2^16范围内的正整数),你可以选择在哪些天购买这支股票。每次购买都必须遵循“低价购买;再低价购买”的原则。写一个程序计算最大购买次数。
    这里是某支股票的价格清单:
        日期 1 2 3 4 5 6 7 8 9 10 11 12
        价格 68 69 54 64 68 64 70 67 78 62 98 87
    最优秀的投资者可以购买最多4次股票,可行方案中的一种是: 
        日期 2 5 6 10
        价格 69 68 64 62
    

    Input

      第1行: N (1 <= N <= 5000),股票发行天数
      第2行: N个数,是每天的股票价格。

    Output

    输出文件仅一行包含两个数:最大购买次数和拥有最大购买次数的方案数(<=2^31)当二种方案“看起来一样”时(就是说它们构成的价格队列一样的时候),这2种方案被认为是相同的。

    Sample Input

    12
    68 69 54 64 68 64 70 67 78 62 98 87

    Sample output

    4 2

    题目第一问与导弹拦截一模一样,第二问需要一点思考。
    假设价格队列长度为 f[i],方案数为 g[i],递推式就为

    $$ g[i]=g[i]+g[j] (f[i]==f[j+1] && price[i]!=price[j])$$

    又因为如果价格队列相同就算相同队列,所以要完善一下:

    $$ g[j]=0 (price[i]price[j] && f[i]f[j]) $$

    代码就是:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int maxn=5010;
    const int inf=1e9;
    int f[maxn];
    int ans,imax,a[maxn],g[maxn];
    
    int read(){
        int Value=0,Base=1;char Ch=getchar();
        for(;!isdigit(Ch);Ch=getchar())if(Ch=='-')Base=-1;
        for(;isdigit(Ch);Ch=getchar())Value=(Value*10)+(Ch^'0');
        return Value*Base;
    }
    
    int main( ){
        int m,n,j,k,i;
        n=read();
        for(i=1;i<=n;i++)
            a[i]=read();
    	ans=-1e9;
        for(i=1;i<=n;i++)f[i]=1;
        for(i=1;i<=n;i++){
            for(j=1;j<i;j++){
                if(a[j]>a[i]){
                    if(f[i]<f[j]+1){
                        ans=max(ans,f[j]+1);
                        f[i]=f[j]+1;
                    }
                }
            }
        }
    	if(ans==-1e9)ans=1;
    	for(i=1;i<=n;i++){
    		if(f[i]==1)g[i]=1;
    		for(j=1;j<i;j++){
    			if(f[i]==f[j] && a[i]==a[j])g[j]=0;
    			if(f[i]==f[j]+1 && a[i]<a[j])g[i]+=g[j];
    		}
    	}
        for(i=1;i<=n;i++)
    		if(f[i]==ans)
    			imax+=g[i];
        printf("%d %d
    ",ans,imax);
        return 0;
    }
    
  • 相关阅读:
    自动化测试先关
    hadoop集群(第二节机器信息分布表)
    hadoop集群(第一节)
    SpringCloud微服务架构学习笔记
    SpringBoot启动一个项目
    VUE框架介绍
    SpringMVC配置与使用
    Spring学习笔记(二)
    8、XML与JSON
    7、主页面访问权限控制
  • 原文地址:https://www.cnblogs.com/ABCDXYZnoip/p/7615763.html
Copyright © 2011-2022 走看看