[日常训练] seq 数列分割

xiaoxiao2021-02-28  7

Description

给出一个长为 n n 的整数数列 aa,要求求出最小的 m m ,使得存在一种将数列分成恰好 KK 份的方案(每份是数列上连续的一段,且不得为空),每份的数字之和不大于 m m 。 对于 100%100% 的数据满足: Kn15000,10000a[i]10000 K ≤ n ≤ 15000 , − 10000 ≤ a [ i ] ≤ 10000

Solution

没有负数就是水题。首先想到二分答案 mid m i d ,考虑如何快速判断。设 bool b o o l 数组 f[i][j] f [ i ] [ j ] 表示分割到序列第 i i 个位置、分成 jj 个部分、满足每个部分和 mid ≤ m i d 是否可能,序列前缀和为 s[i] s [ i ] , 则转移为: f[i][j]|=f[k][j1](0k<i,s[i]s[k]mid)f[0][0]=true f [ i ] [ j ] | = f [ k ] [ j − 1 ] ( 0 ≤ k < i , s [ i ] − s [ k ] ≤ m i d ) , 初 始 时 f [ 0 ] [ 0 ] = t r u e g[i]={f[i][0],f[i][1],...,f[i][K]} g [ i ] = { f [ i ] [ 0 ] , f [ i ] [ 1 ] , . . . , f [ i ] [ K ] } ,定义: g[i]+g[j]={f[i][0] | f[j][0],f[i][1] | f[j][1],...,f[i][K] | f[j][K]} g [ i ] + g [ j ] = { f [ i ] [ 0 ]   |   f [ j ] [ 0 ] , f [ i ] [ 1 ]   |   f [ j ] [ 1 ] , . . . , f [ i ] [ K ]   |   f [ j ] [ K ] } g[i]<<1={0,f[i][0],f[i][1],...,f[i][K1]} g [ i ] << 1 = { 0 , f [ i ] [ 0 ] , f [ i ] [ 1 ] , . . . , f [ i ] [ K − 1 ] } 转移就变为 g[i]=(j=1i1g[j],s[j]s[i]mid)<<1 g [ i ] = ( ∑ j = 1 i − 1 g [ j ] , s [ j ] ≥ s [ i ] − m i d ) << 1 。把 s[i] s [ i ] 按从大到小排序,可知此时合法的 j j 是一个前缀。 因此把 s[i]s[i] 离散化,作为树状数组的下标维护前缀和来转移。此时 DP D P 复杂度 O(nKlogn) O ( n K log ⁡ n ) ,仍然不能通过。进一步地,我们会发现: g[i] g [ i ] 中为 true t r u e 的部分一定是一段连续的区间,只需要用区间的左右边界来表示。容易证明,一开始 g[0]={1,0,...,0} g [ 0 ] = { 1 , 0 , . . . , 0 } 满足条件,之后的转移只涉及上述两种运算,因此也满足条件。此时就可设 g[i]={l[i],r[i]} g [ i ] = { l [ i ] , r [ i ] } ,初始时 g[0]={0,0} g [ 0 ] = { 0 , 0 } ,则: g[i]+g[j]={min{l[i], l[j]}, max{r[i], r[j]}} g [ i ] + g [ j ] = { min { l [ i ] ,   l [ j ] } ,   max { r [ i ] ,   r [ j ] } } g[i]<<1={l[i]+1, min{r[i]+1, K}} g [ i ] << 1 = { l [ i ] + 1 ,   min { r [ i ] + 1 ,   K } } 因此计算 g[i] g [ i ] 的部分就被优化掉了, DP D P 的时间复杂度 O(nlogn) O ( n log ⁡ n )

Code

#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <cctype> using namespace std; namespace inout { const int S = 1 << 20; char frd[S], *ihed = frd + S; const char *ital = ihed; inline char inChar() { if (ihed == ital) fread(frd, 1, S, stdin), ihed = frd; return *ihed++; } inline int get() { char ch; int res = 0; bool flag = false; while (!isdigit(ch = inChar()) && ch != '-'); (ch == '-' ? flag = true : res = ch ^ 48); while (isdigit(ch = inChar())) res = res * 10 + ch - 48; return flag ? -res : res; } }; using namespace inout; const int M = 15005; const int Maxn = 0x3f3f3f3f; int n, K, m, l = Maxn, r = -Maxn, Ans; int f[M], g[M], sum[M], to[M], b[M]; inline int Max(int x, int y) {return x > y ? x : y;} inline int Min(int x, int y) {return x < y ? x : y;} inline void CkMin(int &x, int y) {if (x > y) x = y;} inline void CkMax(int &x, int y) {if (x < y) x = y;} struct line { int l, r; line() {} line(int L, int R): l(L), r(R) {} inline void operator += (const line &x) { CkMin(l, x.l); CkMax(r, x.r); } }c[M], h[M]; const line Inf = line(Maxn, -Maxn); inline line Right(const line &x) { if (x.l == K) return Inf; return line(x.l + 1, Min(x.r + 1, K)); } inline void Modify(int x, const line &y) { for (; x <= n; x += x & -x) c[x] += y; } inline line Query(int x) { line res = Inf; for (; x; x -= x & -x) res += c[x]; return res; } inline bool check(int mid) { for (int i = 1; i <= n; ++i) c[i] = Inf; Modify(to[0], h[0] = line(0, 0)); for (int i = 1; i < n; ++i) { int k = upper_bound(b + 1, b + m + 1, mid - sum[i]) - b - 1; h[i] = Right(Query(k)); Modify(to[i], h[i]); } return h[n - 1].r == K; } int main() { n = get(); K = get(); for (int i = 1; i <= n; ++i) { f[i] = g[i] = sum[i] = get(); sum[i] += sum[i - 1]; b[i] = -sum[i]; if (f[i - 1] > 0) f[i] += f[i - 1]; if (g[i - 1] < 0) g[i] += g[i - 1]; CkMin(l, g[i]); CkMax(r, f[i]); } sort(b + 1, b + n + 2); m = unique(b + 1, b + n + 2) - b - 1; for (int i = 0; i <= n; ++i) to[i] = lower_bound(b + 1, b + m + 1, -sum[i]) - b; ++n; while (l <= r) { int mid = (l + r) / 2; if (check(mid)) Ans = mid, r = mid - 1; else l = mid + 1; } printf("%d\n", Ans); return 0; }
转载请注明原文地址: https://www.6miu.com/read-2500237.html

最新回复(0)