题目大意

有一个长度为 $n$ 的非负整数序列 $a_i$,每次可以选择一段区间减去 $1$,要求选择的区间长度 $\in[l,r]$,问最少多少次把每个位置减成 $0$。

不保证有解,$1 \leq l \leq r \leq n \leq 10^6,\ r – l + 1 \geq \lceil \frac{n}{2} \rceil,\ 0 \leq a_i \leq 10^9$。

解题思路

首先由于每次是对一段区间操作,考虑先差分原序列得到 $c_i=a_i-a_{i-1}(i\in[1,n+1])$。

先从费用流的角度考虑。

源往正权点连权值的流量,负权点往汇连权值的流量,一个可操作区间则表示为,将 $i$ 与 $\in[i+l,i+r]$ 中的点连流量为 INF,费用为 $1$ 的边。

跑最小费用最大流。没流满则无解,否则费用即要求的答案。

怎么优化呢,考虑到这题有个特殊性质 $\ r – l + 1 \geq \lceil \frac{n}{2} \rceil$,也就是说,对于任意满足 $j>i+r$ 的位置,也只需要 $2$ 的费用即可从 $i$ 到 $j$ 流 $1$ 的流量。

于是就贪心地考虑,对于正权点 $i$ ,先对于只要 $1$ 的费用的位置从后往前尽量流,然后再流费用为 $2$ 的位置,如果有正权点或者负权点最后没有满流,就无解。

int main(){
    read(n), read(l), read(r); lfor(i, 1, n) read(a[i]);
    lfor(i, 1, n + 1) c[i] = a[i] - a[i - 1];
    rfor(i, n + 1, 1){
        if(c[i] < 0) Q.push_front(i);
        else if(c[i] > 0){
            while(!Q.empty() && c[i]){
                int x = Q.back(), det = min(c[i], -c[x]);
                c[i] -= det, c[x] += det, Ans += det;
                if(!c[x]) Q.pop_back();
            }
            if(c[i] > sum){ puts("-1"); return 0; }
            Ans += c[i] * 2, sum -= c[i], c[i] = 0;
        }
        while(!Q.empty() && i + r == Q.back()) sum -= c[Q.back()], Q.pop_back();
    }
    if(Q.size() || sum){ puts("-1"); return 0; }
    printf("%lld\n", Ans);
    return 0;
}
//  sto zhy12138 orz
分类: 文章

0 条评论

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注