POJ2373 Dividing The Path 题解

题目描述

约翰的奶牛们发现山脊上的草特别美味。为了维持草的生长,约翰打算安装若干喷灌器。
为简化问题,山脊可以看成一维的数轴,长为 L (1 <= L <= 1,000,000) ,而且L一定是一个偶数。每个喷灌器可以双向喷灌,并有确定的射程,该射程是一个整数,且不短于A,不长于B , A、B(1 <= A <= B <= 1000)都是给出的正整数。它所在位置的两边射程内,都属它的灌溉区域。
现要求山脊的每一个区域都被灌溉到,而且喷灌器的灌溉区域不允许重叠。
约翰有 N (1 <= N <= 1000)只奶牛,每一只都有特别喜爱的草区,第i只奶牛的草区是[Si,Ei],不同奶牛的草区可以重叠。现要求,每只奶牛的草区仅被一个喷灌器灌溉。

一道单调队列优化DP的练手题,下面讲讲思路:

首先提到对于每一个Si--Ei,不能被不同的喷灌器喷灌,那么也就是说Si+1--Ei-1都不能成为任何一个喷灌器的右边界,所以我们开一个数组 l[i] 判断 i 能否成为某一个喷灌器的右边界,若可以,l[i]=false,反之,l[i]=true。

接着,我们考虑状态,定义f[i]表示当覆盖长度为 i 时用的最少的喷灌器的数量,也就是说,i是一个喷灌器的右边界,我们要枚举它的左边界,所以对于每一个合法的i(即l[i]==false),有如下的状态转移方程

$$ f[i]=min(f[j]+1)(A<=(i-j)/2<=B) $$

很明显,这样做的时间复杂度是O(n*n)的,不可取。

仔细研究状态转移方程,我们发现f[i]一定是由满足A<=(i-j)/2<=B且f[j]最小的j转移过来的,所以选用单调队列维护这个最小值。当(i-队首元素下标)/2>B时,队首元素出队。另外还要再开一个队列存储元素编号,只有当这个队列队首元素的下标满足(i-队首元素下标)/2>=A时,才可以将这个元素压进单调队列。

这样就可以优化到O(n)的复杂度了,问题解决。

代码如下:

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    int N,L,A,B,s,e;
    int q[1000001],head=1,tail;
    int p[1000001],h=1,t;
    int f[1000001];//f[i]表示当覆盖长度为i时最少的喷灌器数目 
    bool l[1000001];
    int main()
    {
        scanf("%d%d",&N,&L);
        scanf("%d%d",&A,&B);
        for(register int i=1;i<=N;i+=1)
        {
            scanf("%d%d",&s,&e);
            for(register int j=s+1;j<=e-1;j+=1)l[j]=true;
        }
        memset(f,127,sizeof(f));
        f[0]=0;
        p[++t]=0;
        for(register int i=2;i<=L;i+=2)
        {
            if(l[i]==true)continue;
            while(h<=t&&(i-p[h])/2>=A)
            {
                while(head<=tail&&f[q[tail]]>=f[p[h]])tail--;
                q[++tail]=p[h];
                h++;
            }
            while(head<=tail&&(i-q[head])/2>B)head++;
            if(head<=tail)
            {
                f[i]=f[q[head]]+1;
                p[++t]=i;
            }
        }
        if(f[L]!=2139062143)printf("%d\n",f[L]);
        else printf("-1\n");
        return 0;
}

猜你喜欢

转载自www.cnblogs.com/ForwardFuture/p/9022041.html
今日推荐