Description
- 给出一个长为 n n 的整数数列 ,要求求出最小的 m m ,使得存在一种将数列分成恰好 份的方案(每份是数列上连续的一段,且不得为空),每份的数字之和不大于 m m 。
- 对于 的数据满足: K≤n≤15000,−10000≤a[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
个位置、分成 个部分、满足每个部分和
≤mid
≤
m
i
d
是否可能,序列前缀和为
s[i]
s
[
i
]
, 则转移为:
f[i][j]|=f[k][j−1](0≤k<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][K−1]} g [ i ] << 1 = { 0 , f [ i ] [ 0 ] , f [ i ] [ 1 ] , . . . , f [ i ] [ K − 1 ] }
- 转移就变为 g[i]=(∑j=1i−1g[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 是一个前缀。
- 因此把 离散化,作为树状数组的下标维护前缀和来转移。
- 此时 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;
}