小红书笔试真题 9.13 - 专栏改挂后的最大综合评分(C++/Py/Java /Js/Go)

专栏改挂后的最大综合评分

小红书 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。

思路

枚举 + 二分

  1. 通过枚举x范围为[0,x], 然后二分确定在当前x所能达到的最好ybestY, 由于公式x. c1 + min(y, b) * c2,所以bestY只需要考虑[0, b]即可。
  2. 二分检验逻辑:
    • 判断总数是否满足要求x * b + (n - x) * y是否小于等于总文章数。
    • 判断将x个方向提升至b,以及最小方向数不小于y的操作次数是否满足小于等于k?通过以下方式快速判断:
      1. 提升x个方向不小于b,肯定是优先选择较大的方向。所以可先对a进行升序排序,然后计算后缀差值后缀和。 costB[x - 1] + max(0LL, b - a[n - x])
      2. 所有方向不小于y,可以使用前缀和 + 二分。先对a数组进行前缀和处理prefix,当前a有序,可以通过二分找到第一个>=y的位置p,那么0, p-1就是需要填充的,操作数即为y * p - prefix[p]
      3. 判断cost[x] + (y * p - prefix[p]) <= k
  3. 计算枚举过程中的可行最大值即可。
  4. 算法总体时间复杂度为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)
}
相关推荐
无限码力4 小时前
小红书笔试真题 9.13 - 避重口令(C++/Py/Java /Js/Go)
小红书·小红书笔试·小红书笔试真题·小红书笔试真题题解·小红书最新笔试真题
无限码力2 天前
小红书笔试真题 9.10 - 最小化峰值干扰(C++/Py/Java /Js/Go)
小红书·小红书笔试真题·小红书笔试真题题解·小红书最新笔试真题
无限码力2 天前
小红书笔试真题 9.10 - 双通道评测格网(C++/Py/Java /Js/Go)
小红书笔试·小红书笔试真题·小红书笔试题解·小红书校招笔试题目