[POJ 2373][BZOJ 1986] Dividing the Path

Link:

POJ 2373 传送门

Solution:

一开始想错方向的一道简单$dp$,不应该啊……

我一开始的想法是以$cows' ranges$的节点为状态来$dp$

但明显一个灌溉的区间的两边不一定都在$cows's ranges$上,

因此应该以长为$L$的$field$上的每一个偶数节点为状态来$dp$,

这样转移方程就很容易了:

$dp[i]=min\{ dp[j] \} +1(2*a\le i-j\le 2*b)$

由于$dp[j]$具有决策单调性(对于节点$i$,$k$比$j$更优$(j<k)$,那么对于后面任意一个节点$j$都优于$k$)

于是使用单调队列维护一个$dp$值递增,离起点距离递增的序列,每次弹出$i-q[l]>=2*b$的$l$

为了处理$cows' ranges$上的节点,使用差分将在区间内的节点打上标记,遇到时跳过即可

Note:这里的特判依然要注意,如果对于一个非终点的节点找不到符合的点,不代表无解!跳过即可

Code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>

using namespace std;
const int MAXN=1e6+10;

int n,L,a,b,dp[MAXN],pre[MAXN],q[MAXN],l,r;

int main()
{
    scanf("%d%d%d%d",&n,&L,&a,&b);
    for(int i=1;i<=n;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        pre[x+1]++;pre[y]--;
        if(y-x>2*b){printf("-1");return 0;}
    }
    for(int i=1;i<=L;i++) pre[i]+=pre[i-1];
    
    l=r=1;q[1]=0;
    for(int i=2*a;i<=L;i+=2)
    {
        if(pre[i]) continue;
        while(l<=r && i-q[l]>2*b) l++;
        if(l>r){printf("-1");return 0;}
        if(i-q[l]<2*a) continue; //对不符合条件的数的处理
        
        dp[i]=dp[q[l]]+1;
        while(l<=r && dp[i]<dp[q[r]]) r--;q[++r]=i; 
    }
    if(!dp[L]) printf("-1"); //特例处理 
    else printf("%d",dp[L]);
    return 0;
}

Review:

(1)还是要对特判细节想清楚啊……

(2)对于有决策单调性的题目想到单调性优化

(3)选择$dp$状态时要考虑状态集是否包含了所有“过程”中的“终点”

(如果按$cows' ranges$选取则不能)

猜你喜欢

转载自www.cnblogs.com/newera/p/9157388.html