[CF1799F] Halve or Subtract 题解

题意描述

你被给了一个长度为 n n n 的序列,有两种操作:

  1. 找到一个数,把它除以 2 2 2,随后向上取整。
  2. 找到一个数,把它减去 b b b,随后与 0 0 0 取 max。

每种操作有使用次数上限,对于每个数每种操作只能使用一次,求经过若干次操作后,序列的最小总和。

简要分析

一眼 dp,然后假了,于是思维。

Character 1

一个简单的性质,对于每个数优先进行操作一随后进行操作二比先进行操作二再进行操作一更优

这个是显然的。

Character 2

数越大操作一对答案的贡献也就越大

这个更显然了。

所以我们排个序,这里默认从大到小。

Character 3

随后开始观察样例,发现最后一个样例十分有趣, 4 , 5 , 7 4,5,7 4,5,7 我们需要对 5 5 5 进行操作二,随后对 4 , 7 4,7 4,7 进行操作一才能得到最优答案。

针对这一特殊性质,我们展开举例。

不难发现,对于进行操作二的区间总是被进行操作一的区间所包围

通过观察,进行操作二的区间有且仅有一个。

通过上述三个性质,可以看出我们的操作大致是如下这样的:

接下来我们只需要枚举双操作区间的右端点即初次砍半区间的左端点,与初次砍半区间的长度即可。

代码实现

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>

using namespace std;

typedef long long ll;
const ll maxn = 1e5 + 7;
const ll INF = 1e9 + 7, MOD = 998244353;

inline ll read() {
    char cCc;
    ll xXx = 0, wWw = 1;
    while (cCc < '0' || cCc > '9')
        (cCc == '-') && (wWw = -wWw), cCc = getchar();
    while (cCc >= '0' && cCc <= '9')
        xXx = (xXx << 1) + (xXx << 3) + (cCc ^ '0'), cCc = getchar();
    xXx *= wWw;
    return xXx;
}

inline void write(ll xXx) {
    if (xXx < 0)
        putchar('-'), xXx = -xXx;
    if (xXx > 9)
        write(xXx / 10);
    putchar(xXx % 10 + '0');
}

ll n, b, p, q, a[maxn], ans;
ll f[maxn], g[maxn], h[maxn], s[maxn];

void solve() {
    n = read(), b = read(), p = read(), q = read();
    for (ll i = 1; i <= n; i++)a[i] = read();
    sort(a + 1, a + n + 1, greater<ll>()), ans = 1e18;
    for (ll i = 1; i <= n; i++)
        f[i] = f[i - 1] + max(0ll, (a[i] + 1) / 2 - b), g[i] = g[i - 1] + (a[i] + 1) / 2, h[i] =
                h[i - 1] + max(0ll, a[i] - b), s[i] = s[i - 1] + a[i];
    for (ll i = 0; i <= n; i++)
        for (ll j = 0; j <= n; j++)
            if (p >= i + j && q >= i && p + q - i <= n)
                ans = min(ans, f[i] + g[i + j] - g[i] + h[q + j] - h[i + j] + g[p + q - i] - g[q + j] + s[n] -
                               s[p + q - i]);
    cout << ans << '\n';
}

signed main() {
//    freopen("code.in","r",stdin);
//    freopen("code.out","w",stdout);
    ll T = read();
    while (T--)solve();
    return 0;
}

—— C l a u d e Z . \mathscr{ClaudeZ.} ClaudeZ.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值