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; }