洛谷P1020 导弹拦截第二问【最小路径覆盖】

链接

https://www.luogu.org/problemnew/show/P1020

思路

本题在洛谷上也著有题解,这里跟洛谷上发的基本一致,也可以到本人洛谷博客上去看
https://www.luogu.org/blog/SSL-XXY/

这道题 n2 n 2 nlogn n l o g n 的题解已经够多了,什么dp啊,线段树啊,贪心的都有。但是对于第二问,其实可以用增广路去求

增广路可以过第二问,数据较弱的话可以AC,不过时间不是很快,只能拿到70分。

为什么此解不能AC我却发了这篇题解,因为要去学习更多的做法去做此题,部分分的思路依旧很重要!

我们来引入一下思路

对于第一问,显然跑一遍dp就行了,第二问是问你至少配备几套系统可以拦截所有导弹,也就是用足够多的路径去覆盖这张图,那就转换成了最小路径覆盖问题

根据

定理 1

每一条覆盖路径的边数=覆盖点数-1

定理 2

最小路径覆盖数=顶点数-最大匹配数

关于此解为什么只能拿70分的原因如下

1.明确这是一个有向图(只能是后面的导弹比前面的低或者相等)。然后对于建图需要一遍 n2 n 2 过去,最高也只能拿100分了,这是原因之一

2.对于求增广路,这里用的是匈牙利算法,我们都知道匈牙利算法的时间复杂度较慢是 O(n3) O ( n 3 ) ,尽管通过邻接表优化后它的时间复杂度是一定高于 O(n2) O ( n 2 ) 的,这是原因之二

3.第三个原因不是因为时间,而是因为空间了,对于数据范围 n<=100000 n <= 100000 ,那么我们一共需要建 n×n n × n 条边,这显然会 MLE M L E

以上便是只能拿七十分的原因

代码

#include
#include
#define N 30001
#define M 20000001//最多只能开这么大了
#define r(i,a,b) for(int i=a;i<=b;i++)
#define zero(a) memset(a,0,sizeof(a))//清0
using namespace std;int a[30001],n,f[30001],ans1,ans2;
struct node{int next,to;}edge[M];int l[M],tot,x,y,link[N],ans,t;bool vis[N];//邻接表以及匈牙利算法数组
void add(int u,int v){edge[++tot].to=v;edge[tot].next=l[u];l[u]=tot;}//建边
bool find(int p)//找增广路
{
    for(int i=l[p];i;i=edge[i].next)//访问邻接边
     if(!vis[edge[i].to])
      {
        vis[edge[i].to]=1;
        int q=link[edge[i].to];
        link[edge[i].to]=p;
        if(!q||find(q)) return true;
        link[edge[i].to]=q;
      }
    return false;
}
int main()
{
    f[0]=50234568;
    while(scanf("%d",&a[++n])!=EOF);--n;
    r(i,1,n)//第一问直接用dp过,这里就不详细介绍了
     if(f[ans1]>=a[i])
      f[++ans1]=a[i];
     else
      {
        int l=1,r=ans1;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(f[mid]>=a[i]) l=mid+1;else r=mid-1;
        }
        f[l]=a[i];
      }

    printf("%d\n",ans1);
    r(i,1,n)
     r(j,i+1,n)
      if(a[i]>=a[j]) add(i,j);//若可以接上,则连边
    r(i,1,n)
    {
        zero(vis);//记得每次求增广路要清0
        ans2+=find(i);
    }
    printf("%d",n-ans2);//输出
}

你可能感兴趣的:(GT,图的匹配问题)