HDU 6606 Distribution of books
题意
给出一个长为
n
n
n数组
请将数组分成
k
k
k个连续子串,每个子串和的最大值最小
你可以舍弃数组末尾连续的任意个数
(比如将
−
1
,
−
1
,
−
1
,
−
1
,
12
,
6
-1,-1,-1,-1,12,6
−1,−1,−1,−1,12,6分成4组,我们显然可以舍弃最后两个数)
思路
二分
- 将数组分成k段,显然我们可以考虑二分每段的区间和
LL search() {
LL l = -1e15, r = 1e15, mid, ans = inf;
while (l <= r) {
mid = (l + r) >> 1;
if (check(mid)) {
ans = min(ans, mid);
r = mid - 1;
}
else l = mid + 1;
}
return ans;
}
求区间和为mid的最大段数
- 由于我们可以将末尾任意个数舍弃,所以只要我们求出最多可以分割的段数
≤
k
\leq k
≤k,则方案成立
我们可以考虑动态规划来实现
状态 d p [ i ] dp[i] dp[i]
- 表示截至 a [ i ] a[i] a[i]最多可以划分为几段
状态转移方程
d
p
[
i
]
=
m
a
x
(
d
p
[
j
]
+
1
,
d
p
[
i
]
)
(
s
u
m
[
i
]
−
s
u
m
[
j
]
≤
m
i
d
)
dp[i]=max(dp[j]+1,dp[i])(sum[i]-sum[j]\leq mid)
dp[i]=max(dp[j]+1,dp[i])(sum[i]−sum[j]≤mid)
所有和当前点区间差
≤
m
i
d
\leq mid
≤mid的就可以增加一个区间
比如:
100
,
−
99
,
1000
,
−
999
100, -99 ,1000 ,-999
100,−99,1000,−999
当
m
i
d
=
1
mid=1
mid=1时
显然
d
p
[
0
]
=
0
dp[0]=0
dp[0]=0,因为没有一个前缀和与
s
u
m
[
1
]
sum[1]
sum[1]的差
≤
1
\leq 1
≤1,所以
d
p
[
1
]
=
−
i
n
f
dp[1]=-inf
dp[1]=−inf
而
s
u
m
[
2
]
−
s
u
m
[
0
]
=
1
sum[2]-sum[0]=1
sum[2]−sum[0]=1,所以
a
[
1
]
、
a
[
2
]
a[1]、a[2]
a[1]、a[2]即可构成一个区间 ,因为没有一个前缀和与
s
u
m
[
3
]
sum[3]
sum[3]的差
≤
1
\leq 1
≤1,所以
d
p
[
3
]
=
−
i
n
f
dp[3]=-inf
dp[3]=−inf
而
s
u
m
[
4
]
−
s
u
m
[
2
]
=
1
sum[4]-sum[2]=1
sum[4]−sum[2]=1,所以
a
[
3
]
、
a
[
3
]
a[3]、a[3]
a[3]、a[3]即可构成一个区间,综上最多可以构成
2
2
2个子串
- 对于每个 d p [ i ] dp[i] dp[i]都需要去寻找所有前缀和在区间 [ s u m [ i ] − m i d , s u m [ i ] ] [sum[i]-mid,sum[i]] [sum[i]−mid,sum[i]]的 d p [ j ] dp[j] dp[j],取 d p [ i ] = m a x ( d p [ i ] , d p [ j ] + 1 ) dp[i]=max(dp[i],dp[j]+1) dp[i]=max(dp[i],dp[j]+1)
- 显然复杂度为 O ( n 2 ) O(n^2) O(n2)
- 但对于每个 d p [ i ] dp[i] dp[i]我们我们只要去找 [ s u m [ i ] − m i d , s u m [ i ] ] [sum[i]-mid,sum[i]] [sum[i]−mid,sum[i]]的 d p [ j ] dp[j] dp[j]的最大值即可,我们可以用离散化线段树来维护
离散化线段树维护 m a x ( d p [ j ] ) max(dp[j]) max(dp[j])
- 我们只需要将数组按前缀和离散化
- 然后将数插入线段树时,将其插入离散化后的位置即可
- 如此我们就可以二分找到 [ s u m [ i ] − m i d , s u m [ i ] ] [sum[i]-mid,sum[i]] [sum[i]−mid,sum[i]]的 d p [ j ] dp[j] dp[j]的位置
比如:
3
,
−
2
,
4
,
−
2
3,-2,4,-2
3,−2,4,−2
前缀为:
0
,
3
,
1
,
5
,
3
0,3,1,5,3
0,3,1,5,3
排序为:
0
,
1
,
3
,
3
,
5
0,1,3,3,5
0,1,3,3,5
当
m
i
d
=
2
mid=2
mid=2时,先将0插入线段树0号位置
d
p
[
5
]
=
(
0
,
−
i
n
f
,
−
i
n
f
,
−
i
n
f
,
−
i
n
f
)
dp[5]=(0,-inf,-inf,-inf,-inf)
dp[5]=(0,−inf,−inf,−inf,−inf)
当
i
=
1
i=1
i=1,我们在前缀和中二分
[
1
,
3
]
[1,3]
[1,3]的位置为
[
1
,
3
]
[1,3]
[1,3],最大
d
p
[
j
]
=
−
i
n
f
dp[j]=-inf
dp[j]=−inf
d
p
[
5
]
=
(
0
,
−
i
n
f
,
−
i
n
f
,
−
i
n
f
,
−
i
n
f
)
dp[5]=(0,-inf,-inf,-inf,-inf)
dp[5]=(0,−inf,−inf,−inf,−inf)
当
i
=
2
i=2
i=2,我们在前缀和中二分
[
−
1
,
1
]
[-1,1]
[−1,1]的位置为
[
0
,
1
]
[0,1]
[0,1],最大
d
p
[
j
]
=
0
dp[j]=0
dp[j]=0,
d
p
[
1
]
=
1
dp[1]=1
dp[1]=1
d
p
[
5
]
=
(
0
,
−
i
n
f
,
1
,
−
i
n
f
,
−
i
n
f
)
dp[5]=(0,-inf,1,-inf,-inf)
dp[5]=(0,−inf,1,−inf,−inf)
当
i
=
3
i=3
i=3,我们在前缀和中二分
[
3
,
5
]
[3,5]
[3,5]的位置为
[
2
,
4
]
[2,4]
[2,4],最大
d
p
[
j
]
=
−
i
n
f
dp[j]=-inf
dp[j]=−inf
d
p
[
5
]
=
(
0
,
−
i
n
f
,
1
,
−
i
n
f
,
−
i
n
f
)
dp[5]=(0,-inf,1,-inf,-inf)
dp[5]=(0,−inf,1,−inf,−inf)
当
i
=
4
i=4
i=4,我们在前缀和中二分
[
1
,
3
]
[1,3]
[1,3]的位置为
[
1
,
3
]
[1,3]
[1,3],最大
d
p
[
j
]
=
1
dp[j]=1
dp[j]=1
d
p
[
5
]
=
(
0
,
−
i
n
f
,
1
,
−
i
n
f
,
2
)
dp[5]=(0,-inf,1,-inf,2)
dp[5]=(0,−inf,1,−inf,2)
- 所以我们直接在线段树上实现 d p dp dp的过程即可
- 每次查询 [ s u m [ i ] − m i d , s u m [ i ] ] [sum[i]-mid,sum[i]] [sum[i]−mid,sum[i]]的 d p [ j ] dp[j] dp[j]的最大值
- 然后将 d p [ j ] + 1 dp[j]+1 dp[j]+1加入 i i i的前缀和离散化后的位置
d p dp dp过程
build(1, 0, n);
update(1, p[0], 0);
for (int i = 1; i <= n; i++) {
int K = lower_bound(pre, pre + 1 + n, sum[i] - mid) - pre;
int maxdp = query(1, 0, n, K, n);
update(1, p[i], maxdp + 1);
}
int maxdp = query(1, 0, n, 0, n);
if (maxdp >= k)return true;
return false;
离散化
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
b[i] = pli(sum[i], i);
}
sort(b, b + 1 + n);
for (int i = 0; i <= n; i++) {
p[b[i].second] = i;
pre[i] = b[i].first;
}
完整代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
#pragma warning (disable:4996)
const int maxn = 200005;
const int inf = 0x3f3f3f3f;
int n, k;
int p[maxn];
LL a[maxn], sum[maxn], pre[maxn];
pli b[maxn];
void read() {
memset(pre, 0, sizeof(pre));
memset(b, 0, sizeof(b));
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
sum[i] = sum[i - 1] + a[i];
b[i] = pli(sum[i], i);
}
sort(b, b + 1 + n);
for (int i = 0; i <= n; i++) {
p[b[i].second] = i;
pre[i] = b[i].first;
}
}
struct node
{
int l;
int r;
int data;
node(int l = 0, int r = 0, int d = 0) :l(l), r(r), data(d) {}
}tree[maxn << 2];
void build(int root, int left, int right) {
tree[root].l = left;
tree[root].r = right;
if (left == right) {
tree[root].data = -inf;
return;
}
int mid = (left + right) >> 1;
build(root << 1, left, mid);
build(root << 1 | 1, mid + 1, right);
tree[root].data = max(tree[root << 1].data, tree[root << 1 | 1].data);
}
void update(int root, int x, int val) {
if (tree[root].l == tree[root].r) {
tree[root].data = max(tree[root].data, val);
return;
}
int mid = (tree[root].l + tree[root].r) >> 1;
if (x <= mid) update(root << 1, x, val);
else update(root << 1 | 1, x, val);
tree[root].data = max(tree[root << 1].data, tree[root << 1 | 1].data);
}
int query(int root, int left, int right, int stdl, int stdr) {
if (left >= stdl && right <= stdr) {
return tree[root].data;
}
int mid = (left + right) >> 1, ans = -inf;
if (stdl <= mid) ans = max(ans, query(root << 1, left, mid, stdl, stdr));
if (stdr > mid) ans = max(ans, query(root << 1 | 1, mid + 1, right, stdl, stdr));
return ans;
}
bool check(LL mid) {
build(1, 0, n);
update(1, p[0], 0);
for (int i = 1; i <= n; i++) {
int K = lower_bound(pre, pre + 1 + n, sum[i] - mid) - pre;
int maxdp = query(1, 0, n, K, n);
update(1, p[i], maxdp + 1);
}
int maxdp = query(1, 0, n, 0, n);
if (maxdp >= k)return true;
return false;
}
LL search() {
LL l = -1e15, r = 1e15, mid, ans = inf;
while (l <= r) {
mid = (l + r) >> 1;
if (check(mid)) {
ans = min(ans, mid);
r = mid - 1;
}
else l = mid + 1;
}
return ans;
}
int main() {
int t; scanf("%d", &t);
while (t--) {
read();
LL res = search();
printf("%lld\n", res);
}
}