[HNOI2009]通往城堡之路

无解的情况一定是 (|a_1 - a_n| > (n - 1) imes d),下面考虑优解的情况。

首先这题我们能想出一个简单的费用流模型,但边数和点数都和值域有关,因此这样是过不掉的,可以发现无论怎样 (dp) 状态都要和值域有关,因此这题应该需要考虑贪心。

简单的贪心显然是不正确的,这种需要满足某种条件才合法且需要最小化代价的问题可以考虑使用调整型贪心。即我们确定一个合法解再通过调整使它成为更优解不断进行操作使得其成为最优解。在调整行贪心中,我们一般将初始合法解设置成合法解的一个下界,因为这样既有更大的操作空间也可能从中发现一些性质。例如在本题中我们先不考虑 (a_n) 不动的情况,从 (a_1) 开始不断将初始合法解向下 (-d),即令当前初始解为 (b),那么 (b_i = a_i - (i - 1) imes d),可以发现这是所有位置的一个下界。现在我们的调整策略就是不断抬高某个区间(抬高某个区间再降低某个区间是没有意义的,因为我们可以一开始就不抬高这些降低的区间)。下面我们可以来发掘一些当前合法解的一些性质。

首先一个很重要的性质是我们可以发现每次抬高的区间必定只能是一段后缀,下面我们使用归纳法来证明这个东西。首先第一次抬高操作时,原序列是一个以 (a_1) 开头公差为 (-d) 的等差数列,假设我们抬高一段区间 ([l, r](1 le l le r < n)) 是更优的,因为我们最多两个位置之间相差 (d),因此抬高之后 (r ightarrow r + 1) 之间相差就不止 (d),这样是不合法的,因此第一次抬高我们一定只能抬高一段后缀。那么接下来假设之前抬高的都是一段后缀 ([l, n]) 且方案最优,假设我们现在能抬高一段区间 ([l, r](1 le l le r < n)) 是更优的,分两种情况 (r) 在之前抬高过的一个 (l) 上时,那么我们之前抬高的区间应该加上 ([l, r]) 才是最优的。当 (r) 不再之前抬高过的一个 (l) 上时,可以发现之前每个抬高过的 (l) 之间组成的区间内高度插值依然不变一直是 (d),因此如果我们当前的 (r) 不再之前抬高过的一个 (l) 上,那么我们抬高当前这个区间必然会使得 (r ightarrow r + 1) 差值大于 (d) 而不合法。因此我们就证明了每次抬高的区间一定是一段后缀。

从上面那个证明我们知道,每次我们都要尽可能地将能抬高的后缀全部抬高。但是这个策略还是抽象的,因为我们没有确定抬高的高度,假设我们每次都抬高 (1),当前抬高 ([l, n]) 这个后缀,那么你会发现 (b_i < a_i) 的那些位置都是能抬高的,因此我们记录 (cnt_i = sumlimits_{j = i} ^ n [a_j > b_j]),那么根据我们前面的理论,找到一个最大的 (cnt_i) 然后抬高这段区间是最优的。因此我们只需要每次找到一个 (cnt_i) 最大的位子然后将这段区间抬高 (1) 这样答案就对了。但可以发现这样的复杂度还是和值域有关的的,但于此同时我们又可以发现我们没必要每次不断 (+1),我们可以找到 (cnt_i) 最大的一个 (i) 然后将这段区间加上 (min{a_j - b_j}(i le j le n, a_j ge b_j)) 即可,这样我们每次都会将一个数 (b_i) 加到上限 (a_i),因此这样我们最多加 (n) 次,复杂度就是 (O(n ^ 2)) 的了。注意一下我们每次选取 (cnt_i) 一个最大区间时还要保证 (b_{i - 1} + d > b_i),最后加上的这个值还需要和 (b_{i - 1} + d - b_i)(min),可以发现如果这里 (b_{i - 1} + d - b_i) 是最小值那么我们不会将某个 (b_i) 加到上限,但如果我们在这个位置停下就说明抬高这个位置到 (1) 的这段前缀是不优的,又因为我们将这里加到 (b_{i - 1} + d - b_i) 后下次就不会再选择这个位置为后缀的 (l),因此下一次抬高我们一定会选择右边的点作为 (l),这部分往右的总共调整次数为 (n) 次,所以总共的调整次数还是不超过 (2n) 次的。注意我们满足条件的 (cnt_i) 最大值可能是个负数,因为可能所有 (a_i < b_i(i e n)),所有在记录最大值时初值需要赋值为 (-infin)

#include<bits/stdc++.h>
using namespace std;
#define N 5000 + 5
#define int long long 
#define inf 10000000000000000
#define rep(i, l, r) for(int i = l; i <= r; ++i)
#define dep(i, l, r) for(int i = r; i >= l; --i)
int T, n, d, ans, a[N], b[N];
int read(){
    char c; int x = 0, f = 1;
    c = getchar();
    while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
signed main(){
    T = read();
    while(T--){
        n = read(), d = read(), ans = 0;
        rep(i, 1, n) a[i] = read(); b[1] = a[1];
        if(abs(a[n] - a[1]) > d * (n - 1)){ puts("impossible"); continue;}
        rep(i, 2, n) b[i] = b[i - 1] - d, ans += abs(a[i] - b[i]);
        while(a[n] != b[n]){
            int cnt = 0, P = 0, mi = inf, mx = -inf, h = 0;
            dep(i, 2, n){
                if(b[i] < a[i]) ++cnt, mi = min(mi, a[i] - b[i]);
                else --cnt;
                if(b[i - 1] + d > b[i] && cnt > mx) mx = cnt, h = mi, P = i; 
            }
            h = min(h, b[P - 1] + d - b[P]), ans -= mx * h;
            rep(i, P, n) b[i] += h;
        }
        printf("%lld
", ans);
    }
    return 0;
}
GO!
原文地址:https://www.cnblogs.com/Go7338395/p/13457267.html