Luogu 4409 [ZJOI2006]皇帝的烦恼

BZOJ 1863

lyd口中的夹B递推。

挺妙的解法。

第一个感觉是找到一个最大的相邻的$a_i + a_{i - 1}$就可以了,但是这个想法大概只对了一半,一半的意思是说只有在$n$为偶数的时候才适用,因为只有在偶数的时候,所有数能恰好地被分成两组,两组互不干扰。

奇数的时候就相当于多出了一个$1$,怎么办呢?

显然可以二分答案吧,设$mid$表示当前二分到的颜色的数量。

设$f_i$表示在满足$i - 1$和$i$的限制的条件下,$i$和$1$的颜色冲突的最大个数,再设$g_i$表示这个最小个数,那么有初态$f_1 = g_1 = a_i$。

然后对于$forall i in [2, n]$,有

    $f_i = min(a_i, a_1 - g_{i - 1})$

    $g_i = max(0, a_i - (mid - a_{i - 1} - a_1 + f_{i - 1}))$

解释一下这个式子,$i$最大应该是$i - 1$最小,但是这样的选择又要受到$a_i$的条件的限制,所以取个$min$。

想让$i$最小,但是一定要满足$i - 1$的限制,所以可选的颜色数是$mid - a_{i - 1}$再减掉之前$i - 1$和$1$的最大冲突数$a_1 - f_{i - 1}$,然后还要和$0$取个$max$。

时间复杂度$O(nlogMaxn)$。

Code:

#include <cstdio>
#include <cstring>
using namespace std;

const int N = 20005;
const int inf = 1 << 30;

int n, a[N], f[N], g[N];

inline void read(int &X) {
    X = 0; char ch = 0; int op = 1;
    for(; ch > '9'|| ch < '0'; ch = getchar())
        if(ch == '-') op = -1;
    for(; ch >= '0' && ch <= '9'; ch = getchar())
        X = (X << 3) + (X << 1) + ch - 48;
    X *= op;
}

inline int max(int x, int y) {
    return x > y ? x : y;
}

inline void chkMax(int &x, int y) {
    if(y > x) x = y;
}

inline int min(int x, int y) {
    return x > y ? y : x;
}

inline bool chk(int mid) {
    f[1] = g[1]= a[1];
    for(int i = 2; i <= n; i++) {
        f[i] = min(a[i], a[1] - g[i - 1]);
        g[i] = max(0, a[i] - (mid - a[i - 1] - a[1] + f[i - 1]));
    }
    return !g[n];
}

int main() {
    read(n);
    int ln = 0, rn = inf, mid, res = inf;
    for(int i = 1; i <= n; i++) {
        read(a[i]);
        if(i > 1) chkMax(ln, a[i] + a[i - 1]);
    }
    chkMax(ln, a[1] + a[n]);

    if(n % 2 == 0) return printf("%d
", ln), 0;

    for(; ln <= rn; ) {
        mid = (ln + rn) / 2;
        if(chk(mid)) res = mid, rn = mid - 1;
        else ln = mid + 1;
    }

    printf("%d
", res);
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/CzxingcHen/p/9804048.html