小红书笔试真题 9.17 - 待发热度重排(C++/Py/Java /Js/Go)

待发热度重排

小红书 9月17号 技术岗笔试真题 第二题

题目内容

短视频后台里躺着 mmm 条待发稿,第 iii 条带着质量分 pip_ipi 和热度分 hih_ihi。策划给了一份目标热度排列 t1,t2,...,tmt_1,t_2,\dots,t_mt1,t2,...,tm,要求排完之后每个位置上的热度刚好等于 ttt。

运营只能按下面的规则对调两条稿件:当且仅当 pi≤pjp_i \le p_jpi≤pj 且 hi≤hjh_i \le h_jhi≤hj 时,才允许把第 iii 条和第 jjj 条互换位置。对调可以做任意多次。

问能否让最终的热度序列与 ttt 完全一致。能则输出 YES,不能则输出 NO(大小写必须一致)。

输入描述

第一行一个整数 qqq,表示询问组数。(1≤q≤102)(1 \le q \le 10^{2})(1≤q≤102)

接下来 qqq 组,每组三行:

  • 第一行一个整数 mmm,表示待发条数。(1≤m≤100000)(1 \le m \le 100000)(1≤m≤100000)
  • 第二行 2m2m2m 个整数,按 p1,h1,p2,h2,...,pm,hmp_1,h_1,p_2,h_2,\dots,p_m,h_mp1,h1,p2,h2,...,pm,hm 给出每条稿的质量分与热度分。(1≤pi,hi≤100000)(1 \le p_i,h_i \le 100000)(1≤pi,hi≤100000)
  • 第三行 mmm 个整数 tit_iti,即目标热度排列。(1≤ti≤100000)(1 \le t_i \le 100000)(1≤ti≤100000)
    保证全部询问的 mmm 之和不超过 100000010000001000000。

输出描述

对每组询问输出一行 YES 或 NO。

样例1

输入

复制代码
3
2
1 1 2 3
3 1
2
1 5 2 1
1 5
3
1 4 2 1 3 5
1 4 5

输出

复制代码
YES
NO
YES

说明

  • 第一组:(1,1)(1,1)(1,1) 与 (2,3)(2,3)(2,3) 可以互换,热度能变成 3 13\ 13 1。
  • 第二组:(1,5)(1,5)(1,5) 与 (2,1)(2,1)(2,1) 互不可比,对调不了,热度变不成 1 51\ 51 5。
  • 第三组:三条都能经由可比对调连成一块,热度多重集与目标相同,故可以重排。

样例2

输入

复制代码
2
1
7 7
7
3
1 5 2 3 3 1
1 3 5

输出

复制代码
YES
NO

说明

  • 第一组:只有一条,热度已是 777。
  • 第二组:质量升、热度降,两两不可比,只能保持 5 3 15\ 3\ 15 3 1,对不齐 1 3 51\ 3\ 51 3 5。

思路

枚举 + 二分

  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)
}
相关推荐
卓怡学长1 小时前
w194基于ssm新能源汽车充电系统小程序
java·小程序·intellij-idea
JAVA面经实录9171 小时前
Java高级后端 · 全套面试通关手册(MySQL)
java·mysql·面试
光依旧1 小时前
MCP实战手记(八):从“能跑“到“能上线“——无状态MCP Server的生产落地清单
java·人工智能·spring boot·架构·ai agent·mcp
弹简特2 小时前
【Java项目-企悦抽】15-活动管理模块02-活动创建测试与活动列表实现
java·开发语言·springboot
斯内普吖2 小时前
(开源)水果蔬菜商城实战指南 基于 Java + SSM + Vue + MySQL
java·vue.js·mysql·开源
wang_shu_mo_ran2 小时前
Spring IoC和DI概念篇
java·后端·spring
小狼154543 小时前
拼多多订单数据导出 CSV 实战:接口分页、字段平铺与 5 个数据坑,多多开票助手
java·前端·javascript
Joe_Wang53 小时前
【从0到1学习JVM · 25】同样都要停顿所有线程,Parallel比Serial到底强在哪
java·jvm·学习·垃圾回收
wuminyu3 小时前
JVM虚拟线程的底层实现原理分析
java·linux·c语言·jvm·c++