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;
}