待发热度重排
小红书 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。
思路
枚举 + 二分
- 通过枚举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)
}