(记录好题)P10083 [GDKOI2024 提高组] 不休陀螺

题目跳转:P10083 [GDKOI2024 提高组] 不休陀螺

简要题意

n n n 张牌,每张牌有先代价 a i a_i ai,后贡献 b i b_i bi

对于每一个区间,每次随机抽取一张牌,如果当前费用小于此牌代价,则失败。如果一直随机抽取知道区间中无牌,则区间可取。

n n n 张牌中可取区间的个数 (区间是连续的)。

思维思路

判断一个区间可取

在一个区间内:

  • 如果代价的总和不及贡献总和,即 ∑ i = l r \sum^r_{i=l} i=lr ( b i − a i ) ≥ 0 (b_i-a_i)\ge 0 (biai)0

  • 我们先定义 a i > b i a_i>b_i ai>bi 的牌为亏牌,则 a i ≤ b i a_i\le b_i aibi 为盈牌。我们想要最小化 E E E,那么我们要让最大化 E E E消耗

    有两种方式:

  1. 把所有亏牌放在前面,则刷完亏牌 E E E 会减少 ∑ i = l r \sum^r_{i=l} i=lr ( a i − b i ) [ a i > b i ] (a_i-b_i)[a_i>b_i] (aibi)[ai>bi]

    可以简化为 ∑ i = l r \sum^r_{i=l} i=lr min ⁡ ( 0 , a i − b i ) \min(0,a_i-b_i) min(0,aibi)


  1. 在刷完亏牌后选择一张盈牌加上其代价( a i a_i ai)或亏牌减去其贡献( b i b_i bi),选择最大值并减去。

    因为盈是 a i ≤ b i a_i\le b_i aibi,亏是 a i > b i a_i>b_i ai>bi。容易看出是最大化 min ⁡ ( a i , b i ) \min(a_i,b_i) min(ai,bi)

    总结为 max ⁡ i = l r \max^r_{i=l} maxi=lr min ⁡ ( a i , b i ) \min(a_i, b_i) min(ai,bi)

    最后 E E E 大于等于 0 0 0 则可取。
    E − ∑ i = l r E-\sum^r_{i=l} Ei=lr min ⁡ ( 0 , a i − b i ) ≥ max ⁡ i = l r \min(0,a_i-b_i) \ge \max^r_{i=l} min(0,aibi)maxi=lr min ⁡ ( a i , b i ) ) \min(a_i, b_i)) min(ai,bi))


满足以上两个条件的区间可取。

实现思路

O ( n 2 ) : O(n^2): O(n2): 直接暴力判断每个区间。

O ( n log ⁡ n ) : O(n \log n): O(nlogn):

对于第一个不等式,令 c i = ∑ j = 1 i ( b j − a j ) c_i =\sum_{j=1}^i(b_j-a_j) ci=j=1i(bjaj), 那么只需要满足 c r ≥ c l − 1 c_ r \ge c_{l-1} crcl1

对于第二的不等式,发现其在右端点单调不降,可以使用双指针解决。

具体地,每次将右端点移动最远,将前缀和离散化后使用值域树状数组实现。

代码

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e6 + 10;
const int maxm = __lg(maxn) + 1;

int a[maxn], b[maxn], f[maxm][maxn], n;
long long c[maxn], d[maxn], e;

int qmax(int l, int r) {
	return l > r ? 0 : max(f[__lg(r - l + 1)][l], f[__lg(r - l + 1)][r - (1 << __lg(r - l + 1)) + 1]);
}
struct node {
	int sum[maxn];
	int lowbit (int x) {
		return x & -x;
	}
	void add (int p, int val) {
		for(int i = p; i <= n + 1; i += lowbit(i)) sum[i] += val;
	}
	int query (int p) {
		int res = 0;
		for(int i = p; i > 0; i -= lowbit(i)) res += sum[i];
		return res;
	}
} bit;
int main() {
	cin >> n >> e;
	for (int i = 1; i <= n; ++i) cin >> a[i];
	for (int i = 1; i <= n; ++i) 
	    cin >> b[i], c[i] = c[i - 1] - (a[i] - b[i]), d[i] = c[i];
	sort(d + 1, d + n + 1);
	
	int len = unique(d, d + n + 1) - d;
	for (int i = 0; i <= n; ++i) 
	    c[i] = lower_bound(d, d + len, c[i]) - d + 1;
	    
	for (int i = 1; i <= n; ++i) f[0][i] = min(a[i], b[i]);
	for (int i = 1; 1 << i <= n; ++i) 
	    for (int j = 1; j + (1 << i) - 1 <= n; ++j) 
			f[i][j] = max(f[i - 1][j], f[i - 1][j + (1 << i - 1)]);
	long long ans = 0;
	
	for (int l = 1, r = 1; l <= n; ++l) {
		while (r <= n && qmax(l, r) <= e - max(a[r] - b[r], 0)) 
			bit.add(c[r], 1), e -= max(a[r] - b[r], 0), r++;
			
		ans += bit.query(n + 1) - bit.query(c[l - 1] - 1);
		bit.add(c[l], -1);
		e += max(a[l] - b[l], 0);
	}
	cout << ans << '\n';
}
  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值