二分+DP Codeforces Round 922 1918D Blocking Elements

题意

给定一个长度为 n n n的数组 a a a, 你可以从这 n n n个数中任意选择一部分(也可以不选),假定选择了 m m m个数,那么原数组将被分成 m + 1 m+1 m+1段,把选择的 m m m个数也看作一段,所以共有 m + 2 m+2 m+2段数,对于每段,把段内的所有数字求和,得到 m + 2 m+2 m+2个数,这 m + 2 m+2 m+2个数中的最大值为花费,要求花费最小化。

做法

题目的本质要求是最小化最大值,所以考虑二分。
关键在于如何check当前二分的mid值。
通过研究样例,发现无法通过简单的贪心或者双指针之类的做法进行check, 因为无法保证得到最优解。
所以考虑动态规划。
d p [ i ] dp[i] dp[i]为前 i i i个数且选择了第 i i i个数的合法最小花费(这里指的是选择的数之和的最小值)。
初始有 d p [ 0 ] = 0 dp[0] = 0 dp[0]=0
转移为 d p [ i ] = a [ i ] + min ⁡ ( d p [ j ] ) dp[i] = a[i] + \min(dp[j]) dp[i]=a[i]+min(dp[j]), 其中 j < i j < i j<i并且 ∑ k = j + 1 i − 1 a [ i ] ≤ t a r \sum_{k = j +1}^{i -1}{a[i]} \le tar k=j+1i1a[i]tar, t a r tar tar当前二分的mid值。

  • 为什么不用考虑 j j j左边部分的和小于等于 t a r tar tar?
  • d p [ j ] dp[j] dp[j]已经在前面计算出来,可以保证选择 d p [ j ] dp[j] dp[j] j j j左边的部分是合法的,所以只需要保证 j j j右边到 i − 1 i-1 i1的和合法即可。

答案为 d p [ n + 1 ] dp[n+1] dp[n+1]
转移求最小值可以用优先队列来实现,而 j + 1 j+1 j+1 i − 1 i-1 i1数字之和合法可以用双指针来维护。

时间复杂度粗略为 O ( C ⋅ n ⋅ log ⁡ n ) , C = log ⁡ ( 1 e 14 ) O(C \cdot n \cdot \log n), C = \log{(1e14)} O(Cnlogn),C=log(1e14)

总结

本题的做法是非常经典的二分+DP,即二分答案并使用DP进行check
当有了二分答案的雏形后,构思check时不妨考虑一下是否可以用DP

代码

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using al = array<ll, 2>;

void solve() {
	int n;
	cin >> n;
	vector<ll> a(n + 2);
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
	}
	ll l = 1, r = 1e14;
	auto check = [&](ll tar) {
		vector<ll> dp(n + 2);
		priority_queue<al, vector<al>, greater<al> > q;
		q.push(al{0, 0});
		int L = 0, R = 0;
		ll now = 0;
		while (1) {
			R++;
			if (R >= n + 2)
				break;
			while (L <= n && now > tar)
				now -= a[++L];
			while (!q.empty() && q.top()[1] < L)
				q.pop();
			dp[R] = a[R] + q.top()[0];
			q.push(al{dp[R], R});
			now += a[R];
		}
		return dp[n + 1] <= tar;
	};
	while (l < r) {
		ll mid = (l + r) >> 1;
		// cerr << l << ' ' << r << ' ' << mid << '\n';
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << l << '\n';
}

int main() {
	cin.tie(nullptr) -> sync_with_stdio(false);
	int _;
	cin >> _;
	while (_--) 
		solve();
	return 0;
}

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值