POJ2373 Dividing the Path 题解 单调队列优化DP(附:心路历程)

Posted quanjun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POJ2373 Dividing the Path 题解 单调队列优化DP(附:心路历程)相关的知识,希望对你有一定的参考价值。

题目链接:http://poj.org/problem?id=2373

题目描述

农夫约翰的奶牛们发现了在牧场的山脊上长着味道非常不错的三叶草。为了让这些三叶草得到灌溉,农夫约翰正在山脊上安装洒水器。

为了让安装流程得到简化,每一个洒水器必须沿着山脊进行安装(我们可以将山脊按成一个一维的长度为 (L) 的坐标轴((1 le L le 10^6)(L) 是偶数)
每一个晒水器有一个可调节的灌溉半径 (r) ,它会沿着山脊往左右两个方向灌溉一段距离(也就是说,如果我在坐标为 (i) 的点安装了一个洒水器,且我们设置了这个洒水器的灌溉半径为 (r),那么它的灌溉范围是 ([i-r,i+r]))。
每一个洒水器的灌溉半径必须设置成一个在 ([A,B]) 范围内的整数((1 le A le B le 1000))。
农夫约翰需要安装一些洒水器来浇灌整个山脊,并且要满足:
灌溉区域一方面要覆盖整个山脊(区间 ([0,L]) 都要覆盖),并且要满足山脊中的每一小块(我们称 ([0,1])([1,2])、……、([L-1,L])这些长度为 (1) 的区间为一小块)都只包含在一个洒水器的灌溉范围内(也就是说不存在某一小块被覆盖在多个洒水器的灌溉范围内)。
另一方面不能超出山脊的边界(灌溉区域不能有 (lt 0) 或者 (gt L) 的区域)。

农夫约翰的 (N(1 le N le 1000)) 头奶牛都有一片喜爱的区域,我们用 ([S,E]) 来表示每一只奶牛的喜爱区域(两只奶牛之间可能存在重叠的喜爱区域)。每一只奶牛的喜爱区域必须只被一个洒水器灌溉(也就是说不能出现“对于某一个奶牛喜爱的区域 ([S,E]),存在一个 (i(S le i lt E)),使得 ([S,i]) 在某一个洒水器的灌溉范围,而 ([i+1,E]) 在另一个洒水器的灌溉范围内”的情况),也就是说:

每一个奶牛的喜爱区域 ([S,E]) 必然包含在某一个洒水器的灌溉范围内,但是不一定重叠。

求:
在满足上述条件的情况下最少需要安装多少个不重叠的洒水器?
(两个洒水器在端点处重叠不算重叠,比如:一个洒水器的范围是 ([2,6]),另一个洒水器的范围是 ([6,12]) ,它们在端点上重叠,但是这种不算重叠;但是,如果一个洒水器的范围是 ([2,6]),另一个洒水器的范围是 ([5,10]) ,那么他们在区间 ([5,6]) 上重叠了,这种情况是不合法的)。

输入格式

输入的第一行包含两个整数 (N)(L)(1 le N le 1000, 1 le L le 10^6))。
输入的第二行包含两个整数 (A)(B)(1 le A le B le 1000))。
输入的第 (3 sim N+2) 行,每一行包含两个整数 (S)(E)(0 le S lt E le L)),用于表示某只奶牛喜爱区域的开始和结束坐标。坐标是按照距离山脊的起始位置的偏移量给出的,所以它们的值的范围在 ([0,L]) 之间。

输出格式

输出一个数字,用于表示最少的放置的洒水器的个数。如果不存在合法的放置方案,输出 (-1)

样例输入

2 8
1 2
6 7
3 6

样例输出

3

说明/提示

输入描述:
所有的奶牛都在一个长度为 (8) 的山脊中。洒水器的半径范围是 ([1 .. 2])(1)(2))。一只奶牛喜爱的区间范围是 (3-6),另一只奶牛喜爱的区间范围是 (6-7)

输出描述:
总共需要 (3) 个洒水器:

  • 一个设在 (1),半径为 (1)
  • 一个设在 (4),半径为 (2)
  • 一个设在 (7),半径为 (1)

(2) 个洒水器覆盖了喜爱范围在 (3-6) 的奶牛;第 (3) 个洒水器覆盖了喜爱范围在 (6-7) 的奶牛。

下面是示意图:

                 |-----c2----|-c1|       cows' preferred ranges

     |---1---|-------2-------|---3---|   sprinklers

     +---+---+---+---+---+---+---+---+

     0   1   2   3   4   5   6   7   8

喷水装置在2和6处不被视为重叠。

心路历程

这个真的可以说是新路历程了,因为一开始题目没有读懂,所以仔细翻译了一遍

题目里面有几处可以优化的地方:

每一个洒水器的灌溉范围是偶数,所以只有偶数点可以分割,所以我干脆设状态 (f[i]) 表示区间 ([0, 2 imes i]) 范围被覆盖所需的最少洒水器数量。

当然有些点是不能分割的,即所有 (i) 坐标上有牛的点,所以我可以开一个数组 (c[i]) 表示 (i) 点上有多少个牛覆盖。

只有 (c[i] = 0) 的点 (i) 对应的状态 (f[ lfloor frac{i}{2} floor ]) 才是合法的。

求解所有的 (c[i]) 有一个 (O(L)) 的方法,即:
一开始对于所有的 s,ec[s+1] ++; c[e] --;
然后 c[i] = c[i-1] + c[i]

然后推导状态转移方程,可以得到:

如果 (c[i] lt 0),则(f[i] = 0)
否则

[f[i] = min_{j in [i-B,i-A]}(f[j]) + 1]

并且 (f[j]) 状态要合法。

可以看出,询问区间最值可以用线段树来实现。

所以我们可以用线段树来实现这个程序,时间复杂度为 (O(L cdot log L))

实现代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
using namespace std;
#define INF (1<<29)
const int maxn = 1000010;
int n, L, A, B, s, e, c[maxn], f[maxn];
int minv[maxn<<2];
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
void push_up(int rt) {
    minv[rt] = min(minv[rt<<1], minv[rt<<1|1]);
}
void build(int l, int r, int rt) {
    if (l == r) minv[rt] = INF;
    else {
        int mid = (l + r)/2;
        build(lson); build(rson);
        push_up(rt);
    }
}
void update(int p, int v, int l, int r, int rt) {
    if (l == r) minv[rt] = v;
    else {
        int mid = (l + r)/2;
        if (p <= mid) update(p, v, lson);
        else update(p, v, rson);
        push_up(rt);
    }
}
int query(int L, int R, int l, int r, int rt) {
    if (L <= l && r <= R) return minv[rt];
    int tmp = INF, mid = (l + r)/2;
    if (L <= mid) tmp = query(L, R, lson);
    if (R > mid) tmp = min(tmp, query(L, R, rson));
    return tmp;
}
int main() {
    while (~scanf("%d%d%d%d", &n, &L, &A, &B)) {
        while (n --) {
            scanf("%d%d", &s, &e);
            c[s+1] ++;
            c[e] --;
        }
        for (int i = 1; i <= L; i ++) c[i] += c[i-1];
        build(0, L/2, 1);
        f[0] = 0;
        update(0, 0, 0, L/2, 1);
        for (int i = 1; i <= L/2; i ++) {
            f[i] = INF;
            if (c[i*2]) continue;
            int l = max(0, i-B);
            int r = i-A;
            if (l > r) continue;
            int tmp = query(l, r, 0, L/2, 1);
            if (tmp < INF) {
                f[i] = tmp+1;
                update(i, f[i], 0, L/2, 1);
            }
        }
        if (f[L/2] == INF) puts("-1");
        else printf("%d
", f[L/2]);
    }
    return 0;
}

但是提交上去 TLE 了,这可能有两个原因:

  1. 我自己写超时了;
  2. 这道题目就是卡 TLE 的解法。

然后考虑 DP + 单调队列优化(时间复杂度 (O(L)) )。

这里需要注意,因为区间 ([i-B,i-A])(i) 不一定连续,所以可以采用双指针(国外叫 two points ,国内叫什什么不清楚)解决这个问题。

然后我就AC了。

然后其实我先我一开始也是用的这种方法,但是估计哪里没有思考清楚。

然后我就认真思考了一下这个问题,终于AC了。

实现代码如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
using namespace std;
const int maxn = 1000010;
int n, L, A, B, s, e, c[maxn], f[maxn];
deque<int> que;
int main() {
    while (~scanf("%d%d%d%d", &n, &L, &A, &B)) {
        while (n --) {
            scanf("%d%d", &s, &e);
            c[s+1] ++;
            c[e] --;
        }
        for (int i = 1; i <= L; i ++) c[i] += c[i-1];
        f[0] = 0;
        memset(f+1, -1, sizeof(int)*L/2);
        int j = 0;
        for (int i = 1; i <= L/2; i ++) {
            if (c[i*2]) continue;
            while (j < i-B) j ++;
            while (!que.empty() && que.front() < i-B) que.pop_front();
            for (; j <= i-A; j ++) {
                if (c[j*2] || f[j] == -1) continue;
                while (!que.empty() && f[que.back()] >= f[j])
                    que.pop_back();
                que.push_back(j);
            }
            if (!que.empty())
                f[i] = f[que.front()]+1;
        }
        printf("%d
", f[L/2]);
    }
    return 0;
}

以上是关于POJ2373 Dividing the Path 题解 单调队列优化DP(附:心路历程)的主要内容,如果未能解决你的问题,请参考以下文章

POJ2373 Dividing the Path(单调队列优化dp)

poj2373 Dividing the Path (单调队列+dp)

POJ2373 Dividing the Path 题解 单调队列优化DP(附:心路历程)

poj1014 Dividing (多重背包)

POJ-1014 Dividing

poj 1014 Dividing