loj2392. 「JOISC 2017 Day 1」烟花棒

题意

题目说的很清楚了亚。。把题面再复制一遍吧。

有\(N\)人站在一条数轴上。他们人手一个烟花,每人手中的烟花都恰好能燃烧\(T\)秒。每个烟花只能被点燃一次。
\(1\)号站在原点,\(i\)号(\(1 \leq i \leq N\))到\(1\)号的距离为\(X_i\)。保证\(X_1 = 0\),\(X_1, X_2, ..., X_N\)单调递增(可能有人位置重叠)。
开始时,\(K\)号的烟花刚开始燃烧,其他人的烟花均未点燃。他们的点火工具坏了,只能用燃着的烟花将未点燃的烟花点燃。当两人位置重叠且其中一人手中的烟花燃着时,另一人手中的烟花就可以被点燃。忽略点火所需时间。
求至少需要以多快的速度跑,才能点燃所有人的烟花(此时可能有些人的烟花已经熄灭了)。速度必须是一个非负整数。

note:所求的速度为全程中最大速度的最小值。

题解

本题满足二分性质,我们可以二分一个所求\(v\),然后判断是否有合法方案。

注意到对于一个区间\([L, R]\),且区间中起初有一个烟花是燃烧的,如果可以点燃区间中所有烟花,其中一种方案一定满足,所有人的相对位置不发生改变。因此,在这种方案中,只要满足\(x_L + vT(R - L) \geq x_R - vT(R - L)\)即可。而同样,如果不满足\(x_L + vT(R - L) \geq x_R - vT(R - L)\)这个条件,那么显然整个区间是不能被全部点燃的(以\(v\)的速度)。

观察上面的柿子,移项,得
\[ x_L - 2vTL \geq x_R - 2vTR \]
则令\(a_i = x_i - 2vTi\),则问题变为从区间\([K, K]\)开始向外扩展区间,要保证每个时刻扩展出的区间\([l, r]\)要满足\(a_l \geq a_r\)。当且仅当能扩展到区间\([1, n]\),所有烟花可以被点燃。

观察一个性质:对于可扩展区间\([l, r]\),对于\(i < l\),如果满足:

1.\(a_i \geq a_l\)

2.\(\forall j \in [i, l]\),\(a_j \geq a_r\)

则可知区间\([i, r]\)也是可以扩展的。

就这样,我们可以左右不断做这个操作,每次固定合法区间左(右)端点,向外移动右(左)端点。

如果某次向左向右都无法扩展了,且是因为\(a_i \geq a_l \wedge \exists j \in [i, l], a_j < a_r\)这样的原因,那么说明无法点燃整个大区间\([1, n]\)的烟花。

否则,我们会得到一个极大的区间\([ml, mr]\)(这个区间不能通过上述的方式扩展,且是因为\(\nexists i < l, a_i \geq a_l\))。

这时,我们只能换一种方式来判断——从整个大区间\([1, n]\)向内缩。

此时,对于可缩区间\([l, r]\),对于\(l < i \leq ml\),如果满足:

1.\(a_i \geq a_l\)

2.\(\forall j \in [l, i]\),\(a_j \geq a_r\)

区间\([i, r]\)也可以缩得。

直到区间\([ml, mr]\)如果可以缩得,那么就说明可以点燃所有烟花。

证明?其实这是一个逆向过程,把过程逆一下就挺显然的了。(可以画个折线图试试)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int n, K, T, x[N];
ll a[N];

bool check (int v) {
    for (int i = 1; i <= n; ++i) {
        a[i] = 0ll + x[i] - 2ll * T * v * i;
    }
    if (a[1] < a[n]) {
        return 0;
    }
    int ql = K, qr = K, l, r;
    for (int i = K - 1; i; --i) {
        if (a[i] >= a[ql]) {
            ql = i;
        }
    }
    for (int i = K + 1; i <= n; ++i) {
        if (a[i] <= a[qr]) {
            qr = i;
        }
    }
    for (l = r = K; l != ql || r != qr; ) {
        int ok = 0, L = l, R = r;
        for ( ; L > ql && a[L - 1] >= a[r]; ) {
            if (a[--L] >= a[l]) {
                break;
            }
        }
        if (L < l && a[L] >= a[l]) {
            ok = 1, l = L;
        }
        for ( ; R < qr && a[R + 1] <= a[l]; ) {
            if (a[++R] <= a[r]) {
                break;
            }
        }
        if (R > r && a[R] <= a[r]) {
            ok = 1, r = R;
        }
        if (!ok) {
            return 0;
        }
    }
    for (l = 1, r = n; l != ql || r != qr; ) {
        int ok = 0, L = l, R = r;
        for ( ; L < ql && a[L + 1] >= a[r]; ) {
            if (a[++L] >= a[l]) {
                break;
            }
        }
        if (L > l && a[L] >= a[l]) {
            ok = 1, l = L;
        }
        for ( ; R > qr && a[R - 1] <= a[l]; ) {
            if (a[--R] <= a[r]) {
                break;
            }
        }
        if (R < r && a[R] <= a[r]) {
            ok = 1, r = R;
        }
        if (!ok) {
            return 0;
        }
    }
    return 1;
}
int main () {
    scanf("%d%d%d", &n, &K, &T);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &x[i]);
    }
    int l = 0, r = 1e9, mid, ans = r;
    while (l <= r) {
        mid = (l + r) >> 1;
        if (check(mid)) {
            ans = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    printf("%d\n", ans);
    return 0;
}
上一篇:使用 Python 生成二维码


下一篇:VUE使用QRcode或者vue-qr生成二维码