HDU 6606 Distribution of books 杭电多校第四场 D Distribution of books

19 篇文章 0 订阅
17 篇文章 0 订阅

HDU 6606 Distribution of books

题意

给出一个长为 n n n数组
请将数组分成 k k k个连续子串,每个子串和的最大值最小
你可以舍弃数组末尾连续的任意个数
(比如将 − 1 , − 1 , − 1 , − 1 , 12 , 6 -1,-1,-1,-1,12,6 1111126分成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 100991000999
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 3242
前缀为: 0 , 3 , 1 , 5 , 3 0,3,1,5,3 03153
排序为: 0 , 1 , 3 , 3 , 5 0,1,3,3,5 01335
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]=(0infinfinfinf)
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]=(0infinfinfinf)
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]=(0inf1infinf)
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]=(0inf1infinf)
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]=(0inf1inf2)

  • 所以我们直接在线段树上实现 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);
	}
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值