题意描述
你被给了一个长度为 n n n 的序列,有两种操作:
- 找到一个数,把它除以 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.