POJ 2373 (Dividing the Path)单调队列优化DP

56 篇文章 0 订阅

USACO的数据    

点击打开链接

另一个讲解:sunny606

数据查找:点击打开链接

根据月赛的名称,我们可以写出数据地址。比如08年一月的月赛即是:http://contest.usaco.org/JAN08  这里要注意区分大小写。

题意:

在长为L(<=1000000)的草地(可看成线段)上装喷水头,喷射是以这个喷水头为中心,喷水头的喷洒半径是可调节的,调节范围为[a,b]。要求草地的每个点被且只被一个喷水头覆盖,并且有些连续区间必须被某一个喷水头覆盖而不能由多个喷头分段完全覆盖,求喷水头的最小数

这道题,想到是DP,但是阶段与状态刚开始没搞明白。想到将每个区间当作一个阶段,这样过去。但是问题依旧很多,没有摆脱对前一个点位置的限定。其实会看题意。是要求完全覆盖,多出的洒水范围是不允许的,那么从0到len中应该是完全覆盖。那么,

定义dp[i]为覆盖[0,i]区间所需的的最小喷头数。dp[i]=min( dp[j] )+1,  i-2*b<=j<=i-2*a.   因为一个喷头可以解决的范围至多是2*a~2*b,那么将其之前那些用阶段分开。前面 i-j 个都放好了...然后再在 j中放一个使得这一段全被覆盖。

因为一个喷头解决的区间必然是偶数,所以跳过所有的奇数。

还有就是奶牛喜欢的区域只能用一个喷头。那么奶牛区间就不会被完全覆盖(完全覆盖了就将区间分开了),跳过去。

原始版本TLE:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define L 1001000
#define inf 0x3f3f3f3f
using namespace std;
int a,b,n,l,dp[L],Q[L],head,tail,size;

int dpro()
{
    dp[0]=0;
    for(int i=2;i<=l;i+=2)
    {
        if(dp[i]<=inf)
        {
            for(int j=a;j<=b;j++)
            {
                int k=i-j-j;
                if(k<0) break;
                dp[i]=min(dp[i],dp[k]);
            }
            dp[i]++;
        }
    }
    if(dp[l]>=inf) return -1;
    return dp[l];
}
int main()
{
    while (scanf("%d%d", &n, &l)!=EOF) {

          scanf("%d%d", &a, &b);
          memset(dp,0x3f,sizeof(dp));

          for(int i=0; i<n; i++) {
                int s, e;
                scanf("%d%d", &s, &e);
                for(int j=s+1; j<e; j++) dp[j] = inf+1;//这些区间不用考虑
          }
          if(l&1==1) printf("-1\n");
          else printf("%d\n", dpro());
    }
    return 0;
}

运用单调队列优化

转移方程dp[i]=min( dp[j] ) +1,可以看出每次只需要对 i-2*b~i-2*a 之间选取最优解,单调队列可以很好满足需求。


#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define L 1001000
#define inf 0x3f3f3f3f
using namespace std;
int a,b,n,l,dp[L],Q[L],head,tail,siz;

void in(int n)
{
    while(head<tail && dp[Q[tail-1]]>=dp[n]) tail--;//从后往前插入
    Q[tail++]=n;
    while(n-Q[head]>siz) head++;//保持可选区间
}
int dpro()
{
    head=tail=0;
    dp[0]=0;
    siz=2*b-2*a;
    for(int i=2;i<2*a;i+=2) dp[i]=inf+1;
    for(int i=2*a;i<=l;i+=2)
    {
        in(i-2*a);
        if(dp[i]<=inf)
            dp[i]=dp[Q[head]]+1;//刚开始这里错了,head后面写了++
    }
    if(dp[l]>=inf) return -1;
    return dp[l];
}
int main()
{
    while (scanf("%d%d", &n, &l)!=EOF) {

          scanf("%d%d", &a, &b);
          memset(dp,0x3f,sizeof(dp));

          for(int i=0; i<n; i++) {
                int s, e;
                scanf("%d%d", &s, &e);
                for(int j=s+1; j<e; j++) dp[j] = inf+1;
          }
          if(l&1==1) printf("-1\n");
          else printf("%d\n", dpro());
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值