导弹拦截的另解

导弹拦截是很老很经典的dp入门题了,它的主要方法如n^2dp和队列优化都是很入门的方法。

但是如果导弹拦截问题转到多维空间(如三维),即维度不只有高度,还可能有多个参数限制时,这个问题又该如何解决呢?

这时候需要用到图论的知识了。

第一问是最长序列,与正常n^2dp的做法一样。但是第二问并不能更换符号再次dp来实现(包括队列优化也不行,可以自己试一下)。这时我们注意到,导弹拦截的过程只可能不断向后拦截,不可能拦截完后面的导弹在回头拦截前面的导弹,所以可以将能够连续拦截的导弹之间建立有向边(前指向后),可以通过前面发现的特点证明它一定是一个DAG(有向无环图)。这时候想要找到最少系统数,即求DAG上的最小覆盖。而DAG的最小覆盖=点数-最大匹配数(二分图匹配基本定理),所以这道题转换成了求二分图最大匹配。方法也很直观,将每颗导弹拆为2个点,将能够连续拦截的导弹之间建立有向边(前的左端点指向后的右端点),然后跑一遍匈牙利就可以了。ps:因为是这一题的题解所以打的是二维的(时间与高度),但其实多维的也能一样解决。只需要更改a数组的判断就行了。

代码(内含注释):

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<map>//庞大的库(然而大部分都没用上)
#define For(i,a,b) for(i=(a);i<=(b);++i)
#define Forward(i,a,b) for(i=(a);i>=(b);--i)
using namespace std;
const int MAXN=150;
template<typename T>
inline void read(T &x)//没有用上却仍然挂着的读入优化
{
    T s=0,f=1;
    char k=getchar();
    while(!isdigit(k)&&k!='-')k=getchar();
    if(k=='-')
    {
        f=-1;
        k=getchar();
    }
    while(isdigit(k))
    {
        s=(s<<3)+(s<<1)+(k^'0');
        k=getchar();
    }
    x=s*f;
}
struct edge
{
    int v,next;
}p[MAXN*MAXN];//边
int n=1,a[MAXN],dp[MAXN],head[MAXN],e,dir[MAXN];//数组分别记录高度,dp数组,列式前向星的头和匈牙利的match数组
bool vis[MAXN];//记录是否访问
void add(int u,int v)//加入边
{
    p[++e].v=v;
    p[e].next=head[u];
    head[u]=e;
}
bool dfs(int x)//匈牙利,不清楚的去翻百度
{
    int v=head[x];
    while(v)
    {
        if(!vis[p[v].v])
        {
            vis[p[v].v]=true;
            if(!dir[p[v].v]||dfs(dir[p[v].v]))
            {
                dir[p[v].v]=x;
                return true;
            }
        }
        v=p[v].next;
    }
    return false;
}
int main(void)
{
    while(scanf("%d",&a[n])!=EOF)++n;//处理输入
    --n;
    a[0]=0xFFFFFFF;
    int i,j,ans=0;
    For(i,1,n)
       For(j,0,i-1)if(a[i]<a[j])//基础的dp
       {
            if(j)add(j,i);//加入边
            ans=max(ans,dp[i]=max(dp[i],dp[j]+1));//不要觉得很高级,其实就是基础dp的取最大值
       }
    cout<<ans<<endl;//输出第一问答案
    ans=n;//处理第二问
    For(i,1,n)//匈牙利求最大匹配
    {
        memset(vis,0,sizeof(vis));
        if(dfs(i))--ans;
    }
    cout<<ans<<endl;
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值