专栏改挂后的最大综合评分
小红书 9月13号 笔试真题 第二题
题目内容
某技术社区举办「全能作者季」。你在 nnn 个专栏方向上各有若干成稿:第 iii 个方向现有 aia_iai 篇。
你可以把一篇成稿从方向 iii 改挂到方向 jjj(须保证该方向改挂后篇数非负)。每改挂一篇计一次操作,最多操作 kkk 次。
改挂结束后,设 xxx 为成稿数不少于 bbb 篇的方向个数,y=min(a1,a2,...,an)y=\min(a_1,a_2,\dots,a_n)y=min(a1,a2,...,an),综合评分定义为
x⋅c1+min(y,b)⋅c2x\cdot c_1+\min(y,b)\cdot c_2x⋅c1+min(y,b)⋅c2
保证 c1≥c2c_1\ge c_2c1≥c2。请最大化综合评分。
输入描述
第一行五个整数 nnn、kkk、bbb、c1c_1c1、c2c_2c2。
第二行 nnn 个整数 a1,a2,...,ana_1,a_2,\ldots,a_na1,a2,...,an。
输出描述
输出一个整数,表示可达到的综合评分最大值。
约束条件
- 1≤n≤1051\le n\le 10^51≤n≤105
- 0≤k,ai≤1090\le k,a_i\le 10^90≤k,ai≤109
- 1≤b,c1,c2≤1091\le b,c_1,c_2\le 10^91≤b,c1,c2≤109
- c1≥c2c_1\ge c_2c1≥c2
样例1
输入
5 3 4 4 1
1 2 5 0 3
输出
9
说明
一种方案:从第 333 个方向改挂 111 篇到第 555 个方向,再从第 222 个方向改挂 111 篇到第 444 个方向,各方向成稿数为 1,1,4,1,41,1,4,1,41,1,4,1,4。此时 x=2x=2x=2,y=1y=1y=1,min(y,b)=1\min(y,b)=1min(y,b)=1,评分为 2×4+1×1=92\times 4+1\times 1=92×4+1×1=9。
思路
枚举 + 二分
- 通过枚举x范围为
[0,x], 然后二分确定在当前x所能达到的最好ybestY, 由于公式x. c1 + min(y, b) * c2,所以bestY只需要考虑[0, b]即可。 - 二分检验逻辑:
- 判断总数是否满足要求
x * b + (n - x) * y是否小于等于总文章数。 - 判断将x个方向提升至b,以及最小方向数不小于y的操作次数是否满足小于等于k?通过以下方式快速判断:
- 提升x个方向不小于b,肯定是优先选择较大的方向。所以可先对
a进行升序排序,然后计算后缀差值后缀和。costB[x - 1] + max(0LL, b - a[n - x]) - 所有方向不小于y,可以使用前缀和 + 二分。先对a数组进行前缀和处理
prefix,当前a有序,可以通过二分找到第一个>=y的位置p,那么0, p-1就是需要填充的,操作数即为y * p - prefix[p] - 判断
cost[x] + (y * p - prefix[p]) <= k
- 提升x个方向不小于b,肯定是优先选择较大的方向。所以可先对
- 判断总数是否满足要求
- 计算枚举过程中的可行最大值即可。
- 算法总体时间复杂度为
O(nlogblogn)
C++
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
ll k, b, c1, c2;
cin >> n >> k >> b >> c1 >> c2;
vector<ll> a(n);
ll total = 0;
for (ll &x : a) {
cin >> x;
total += x;
}
sort(a.begin(), a.end());
// 前缀和
vector<ll> pre(n + 1, 0);
for (int i = 0; i < n; i++) {
pre[i + 1] = pre[i] + a[i];
}
// costB[x]:
// 选择原本文章数最多的 x 个方向,将它们提升到 b
// 所需要补充的最少文章数
vector<ll> costB(n + 1, 0);
for (int x = 1; x <= n; x++) {
costB[x] = costB[x - 1] + max(0LL, b - a[n - x]);
}
auto check = [&](int x, ll y) -> bool {
// 最终至少需要:
// x 个方向达到 b
// 其余 n-x 个方向至少达到 y
__int128 need = (__int128)x * b +
(__int128)(n - x) * y;
// 总文章数不够
if (need > total) {
return false;
}
// 选择最大的 x 个方向提升到 b
ll cost = costB[x];
if (cost > k) {
return false;
}
// 剩余 n-x 个方向至少达到 y
int m = n - x;
// 找到前 m 个方向中第一个 >= y 的位置
int p = lower_bound(a.begin(), a.begin() + m, y) - a.begin();
// a[0...p-1] 都小于 y,需要补到 y
cost += 1LL * p * y - pre[p];
return cost <= k;
};
ll ans = 0;
for (int x = 0; x <= n; x++) {
// -1 表示当前 x 没有任何可行的 y
ll bestY = -1;
ll left = 0;
ll right = b;
while (left <= right) {
ll mid = left + (right - left) / 2;
if (check(x, mid)) {
bestY = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
// 当前 x 存在可行的 y
if (bestY != -1) {
ans = max(ans, 1LL * x * c1 + bestY * c2);
}
}
cout << ans << '\n';
return 0;
}
java
java
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long k = sc.nextLong();
long b = sc.nextLong();
long c1 = sc.nextLong();
long c2 = sc.nextLong();
long[] a = new long[n];
long total = 0;
for (int i = 0; i < n; i++) {
a[i] = sc.nextLong();
total += a[i];
}
Arrays.sort(a);
// 前缀和
long[] pre = new long[n + 1];
for (int i = 0; i < n; i++) {
pre[i + 1] = pre[i] + a[i];
}
// costB[x]:
// 选择原本文章数最多的 x 个方向,将它们提升到 b
// 所需要补充的最少文章数
long[] costB = new long[n + 1];
for (int x = 1; x <= n; x++) {
costB[x] = costB[x - 1] + Math.max(0L, b - a[n - x]);
}
long ans = 0;
for (int x = 0; x <= n; x++) {
// -1 表示当前 x 没有任何可行的 y
long bestY = -1;
long left = 0;
long right = b;
while (left <= right) {
long mid = left + (right - left) / 2;
// 最终至少需要:
// x 个方向达到 b
// 其余 n-x 个方向至少达到 y
// 使用 long 即可覆盖本题范围
long need = (long) x * b +
(long) (n - x) * mid;
boolean ok = true;
// 总文章数不够
if (need > total) {
ok = false;
}
// 选择最大的 x 个方向提升到 b
long cost = costB[x];
if (cost > k) {
ok = false;
}
if (ok) {
// 剩余 n-x 个方向至少达到 y
int m = n - x;
// 找到前 m 个方向中第一个 >= y 的位置
int p = lowerBound(a, m, mid);
// a[0...p-1] 都小于 y,需要补到 y
cost += (long) p * mid - pre[p];
if (cost > k) {
ok = false;
}
}
if (ok) {
bestY = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
// 当前 x 存在可行的 y
if (bestY != -1) {
ans = Math.max(ans, (long) x * c1 + bestY * c2);
}
}
System.out.println(ans);
}
// 在 a[0...end-1] 中查找第一个 >= target 的位置
static int lowerBound(long[] a, int end, long target) {
int left = 0;
int right = end;
while (left < right) {
int mid = left + (right - left) / 2;
if (a[mid] >= target) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
}
python
python
import sys
s = sys.stdin.readline().split()
n = int(s[0])
k = int(s[1])
b = int(s[2])
c1 = int(s[3])
c2 = int(s[4])
a = list(map(int, sys.stdin.readline().split()))
total = sum(a)
a.sort()
# 前缀和
pre = [0] * (n + 1)
for i in range(n):
pre[i + 1] = pre[i] + a[i]
# costB[x]:
# 选择原本文章数最多的 x 个方向,将它们提升到 b
# 所需要补充的最少文章数
costB = [0] * (n + 1)
for x in range(1, n + 1):
costB[x] = costB[x - 1] + max(0, b - a[n - x])
ans = 0
for x in range(n + 1):
# -1 表示当前 x 没有任何可行的 y
bestY = -1
left = 0
right = b
while left <= right:
mid = left + (right - left) // 2
# 最终至少需要:
# x 个方向达到 b
# 其余 n-x 个方向至少达到 y
need = x * b + (n - x) * mid
# 总文章数不够
if need > total:
right = mid - 1
continue
# 选择最大的 x 个方向提升到 b
cost = costB[x]
if cost > k:
right = mid - 1
continue
# 剩余 n-x 个方向至少达到 y
m = n - x
# 找到前 m 个方向中第一个 >= y 的位置
p = 0
l = 0
r = m
while l < r:
middle = l + (r - l) // 2
if a[middle] >= mid:
r = middle
else:
l = middle + 1
p = l
# a[0...p-1] 都小于 y,需要补到 y
cost += p * mid - pre[p]
if cost <= k:
bestY = mid
left = mid + 1
else:
right = mid - 1
# 当前 x 存在可行的 y
if bestY != -1:
ans = max(ans, x * c1 + bestY * c2)
print(ans)
javascript
js
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const input = [];
rl.on('line', (line) => {
input.push(line.trim());
});
rl.on('close', () => {
const first = input[0].split(/\s+/).map(Number);
const n = first[0];
const k = first[1];
const b = first[2];
const c1 = first[3];
const c2 = first[4];
const a = input[1].split(/\s+/).map(Number);
let total = 0;
for (const x of a) {
total += x;
}
a.sort((x, y) => x - y);
// 前缀和
const pre = new Array(n + 1).fill(0);
for (let i = 0; i < n; i++) {
pre[i + 1] = pre[i] + a[i];
}
// costB[x]:
// 选择原本文章数最多的 x 个方向,将它们提升到 b
// 所需要补充的最少文章数
const costB = new Array(n + 1).fill(0);
for (let x = 1; x <= n; x++) {
costB[x] = costB[x - 1] + Math.max(0, b - a[n - x]);
}
// 在 a[0...end-1] 中查找第一个 >= target 的位置
function lowerBound(end, target) {
let left = 0;
let right = end;
while (left < right) {
const mid = Math.floor((left + right) / 2);
if (a[mid] >= target) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
let ans = 0;
for (let x = 0; x <= n; x++) {
// -1 表示当前 x 没有任何可行的 y
let bestY = -1;
let left = 0;
let right = b;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
// 最终至少需要:
// x 个方向达到 b
// 其余 n-x 个方向至少达到 y
const need = x * b + (n - x) * mid;
// 总文章数不够
if (need > total) {
right = mid - 1;
continue;
}
// 选择最大的 x 个方向提升到 b
let cost = costB[x];
if (cost > k) {
right = mid - 1;
continue;
}
// 剩余 n-x 个方向至少达到 y
const m = n - x;
// 找到前 m 个方向中第一个 >= y 的位置
const p = lowerBound(m, mid);
// a[0...p-1] 都小于 y,需要补到 y
cost += p * mid - pre[p];
if (cost <= k) {
bestY = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
// 当前 x 存在可行的 y
if (bestY !== -1) {
ans = Math.max(ans, x * c1 + bestY * c2);
}
}
console.log(ans);
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
"sort"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var n int
var k, b, c1, c2 int64
fmt.Fscan(in, &n, &k, &b, &c1, &c2)
a := make([]int64, n)
var total int64
for i := 0; i < n; i++ {
fmt.Fscan(in, &a[i])
total += a[i]
}
sort.Slice(a, func(i, j int) bool {
return a[i] < a[j]
})
// 前缀和
pre := make([]int64, n+1)
for i := 0; i < n; i++ {
pre[i+1] = pre[i] + a[i]
}
// costB[x]:
// 选择原本文章数最多的 x 个方向,将它们提升到 b
// 所需要补充的最少文章数
costB := make([]int64, n+1)
for x := 1; x <= n; x++ {
v := b - a[n-x]
if v < 0 {
v = 0
}
costB[x] = costB[x-1] + v
}
// 在 a[0...end-1] 中查找第一个 >= target 的位置
lowerBound := func(end int, target int64) int {
left, right := 0, end
for left < right {
mid := left + (right-left)/2
if a[mid] >= target {
right = mid
} else {
left = mid + 1
}
}
return left
}
var ans int64
for x := 0; x <= n; x++ {
// -1 表示当前 x 没有任何可行的 y
var bestY int64 = -1
var left int64 = 0
var right int64 = b
for left <= right {
mid := left + (right-left)/2
// 最终至少需要:
// x 个方向达到 b
// 其余 n-x 个方向至少达到 y
need := int64(x)*b + int64(n-x)*mid
// 总文章数不够
if need > total {
right = mid - 1
continue
}
// 选择最大的 x 个方向提升到 b
cost := costB[x]
if cost > k {
right = mid - 1
continue
}
// 剩余 n-x 个方向至少达到 y
m := n - x
// 找到前 m 个方向中第一个 >= y 的位置
p := lowerBound(m, mid)
// a[0...p-1] 都小于 y,需要补到 y
cost += int64(p)*mid - pre[p]
if cost <= k {
bestY = mid
left = mid + 1
} else {
right = mid - 1
}
}
// 当前 x 存在可行的 y
if bestY != -1 {
score := int64(x)*c1 + bestY*c2
if score > ans {
ans = score
}
}
}
fmt.Fprintln(out, ans)
}