题目## 题目
题目链接
题目描述
有一款著名的大型多人电子竞技游戏网站"喜爱福",网站通常会举办一些比赛。通常一名参赛选手只有一个账号,但不难猜到,总会有人"开小号"上分。
小苯就是一位该游戏的忠实玩家,他总共有 n n n 个账号,每个账号的分数分别为 a i a_i ai。他深谙游戏中一位著名玩家 st****lk 的一句名言:“只要你永远打分更低的号,那么你的最高分就是单调不降”。
现在我们记录了小苯 m m m 次的比赛记录,已知小苯每次都会谨记 st****lk 的名言,从而使用分数最低的账号参赛。现在我们想知道小苯每次参赛后,他的最高分是多少。
输入:
- 第一行两个正整数 n , m n,m n,m,分别表示小苯的账号个数和新参加的比赛记录数
- 第二行 n n n 个整数 a i a_i ai,表示小苯每个账号目前的分数
- 第三行 m m m 个整数 b i b_i bi,分别表示小苯每次比赛后,分数的变化值
输出:
- 输出 m m m 行,每行一个整数,表示小苯参与完第 i i i 场比赛后,他的最高分的值
解题思路
这是一个模拟问题,可以通过以下步骤解决:
-
关键发现:
- 每次都使用分数最低的账号参赛
- 需要维护所有账号的分数
- 每次比赛后需要更新最高分
-
模拟策略:
- 使用优先队列(小根堆)维护所有账号分数
- 每次从堆顶取出最小分数的账号
- 更新该账号分数后重新入堆
- 维护当前最高分
-
具体步骤:
- 初始化优先队列和最高分
- 对每场比赛:
- 取出最小分数
- 加上比赛分数变化
- 更新最高分
- 将新分数入堆
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
priority_queue<int, vector<int>, greater<int>> pq; // 小根堆
int max_score = 0;
// 读入初始分数
for(int i = 0; i < n; i++) {
int score;
cin >> score;
pq.push(score);
max_score = max(max_score, score);
}
// 处理每场比赛
for(int i = 0; i < m; i++) {
int delta;
cin >> delta;
int min_score = pq.top(); // 取出最小分数
pq.pop();
min_score += delta; // 更新分数
pq.push(min_score);
max_score = max(max_score, min_score); // 更新最高分
cout << max_score << endl;
}
return 0;
}
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小根堆
int maxScore = 0;
// 读入初始分数
for(int i = 0; i < n; i++) {
int score = sc.nextInt();
pq.offer(score);
maxScore = Math.max(maxScore, score);
}
// 处理每场比赛
for(int i = 0; i < m; i++) {
int delta = sc.nextInt();
int minScore = pq.poll(); // 取出最小分数
minScore += delta; // 更新分数
pq.offer(minScore);
maxScore = Math.max(maxScore, minScore); // 更新最高分
System.out.println(maxScore);
}
}
}
import heapq
n, m = map(int, input().split())
scores = list(map(int, input().split()))
deltas = list(map(int, input().split()))
heapq.heapify(scores) # 转换为小根堆
max_score = max(scores) # 初始最高分
# 处理每场比赛
for delta in deltas:
min_score = heapq.heappop(scores) # 取出最小分数
min_score += delta # 更新分数
heapq.heappush(scores, min_score)
max_score = max(max_score, min_score) # 更新最高分
print(max_score)
算法及复杂度
- 算法:优先队列(小根堆)
- 时间复杂度: O ( m log n ) \mathcal{O}(m \log n) O(mlogn) - 每场比赛需要一次堆操作
- 空间复杂度: O ( n ) \mathcal{O}(n) O(n) - 需要存储所有账号分数
题目链接
题目描述
小苯有一个长度为 n n n 的数组 a a a,他想要使得数组有序(单调不降)。为此,他必须选择一段区间 [ l , r ] [l,r] [l,r],将数组的这一段删除,其他的部分(如果存在的话)就按顺序拼在一起。
现在他想知道有多少种不同的选择区间的方案。注:小苯认为,空数组也满足有序,即你可以选择 [ 1 , n ] [1,n] [1,n] 这个区间。
输入:
- 第一行一个正整数 n n n,表示数组的长度
- 第二行 n n n 个正整数 a i a_i ai,表示数组 a a a
输出:
- 一个正整数表示答案
解题思路
这是一个区间问题,可以通过以下步骤优化解决:
-
关键发现:
- 对于每个位置 i,我们可以预处理:
- p[i] 表示从位置 i 向左最多有多少个连续递增的数
- s[i] 表示从位置 i 向右最多有多少个连续递增的数
- 如果要删除区间 [l,r],那么 l-1 和 r+1 位置的数必须能够连接
- 对于每个位置 i,我们可以预处理:
-
优化策略:
- 枚举左端点 i,如果 p[i-1] < i-1,说明左边部分不是递增的,可以直接退出
- 对于每个左端点,二分查找右端点:
- 找到最小的位置 j,使得 s[j] = n-j+1(右边部分递增)且 a[i-1] <= a[j]
- 从 j 到 n 的所有位置都可以作为右端点
-
具体步骤:
- 预处理 p 和 s 数组
- 枚举左端点,二分查找右端点
- 统计合法方案数
代码
#include <bits/stdc++.h>
using namespace std;
const int inf = 2e9;
void solve() {
int n;
cin >> n;
vector<int> a(n + 2);
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
a[n + 1] = inf; // 添加哨兵,简化边界处理
// 预处理连续递增长度
vector<int> p(n + 1), s(n + 2);
for(int i = 1; i <= n; i++) {
if(a[i] >= a[i - 1]) {
p[i] = p[i - 1] + 1;
} else {
p[i] = 1;
}
}
for(int i = n; i; i--) {
if(a[i] <= a[i + 1]) {
s[i] = s[i + 1] + 1;
} else {
s[i] = 1;
}
}
int ans = 0;
for(int i = 1; i <= n; i++) {
int x = a[i - 1];
if(p[i - 1] < i - 1) break; // 左边部分不递增,后面都不用看了
// 二分查找右端点
int l = i, r = n + 1;
while(l < r) {
int mid = l + r >> 1;
if(s[mid] == (n - mid + 1) && x <= a[mid]) r = mid;
else l = mid + 1;
}
ans += n - l + 1; // l到n的所有位置都可以作为右端点
ans += l > i; // 如果l>i,说明可以不删除i位置
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
solve();
return 0;
}
import java.io.*;
import java.util.*;
public class Main {
static final int INF = (int)2e9;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
StringTokenizer st = new StringTokenizer(br.readLine());
int[] a = new int[n + 2];
for(int i = 1; i <= n; i++) {
a[i] = Integer.parseInt(st.nextToken());
}
a[n + 1] = INF;
// 预处理连续递增长度
int[] p = new int[n + 1];
int[] s = new int[n + 2];
for(int i = 1; i <= n; i++) {
if(a[i] >= a[i - 1]) {
p[i] = p[i - 1] + 1;
} else {
p[i] = 1;
}
}
for(int i = n; i > 0; i--) {
if(a[i] <= a[i + 1]) {
s[i] = s[i + 1] + 1;
} else {
s[i] = 1;
}
}
int ans = 0;
for(int i = 1; i <= n; i++) {
int x = a[i - 1];
if(p[i - 1] < i - 1) break;
// 二分查找右端点
int l = i, r = n + 1;
while(l < r) {
int mid = (l + r) >> 1;
if(s[mid] == (n - mid + 1) && x <= a[mid]) r = mid;
else l = mid + 1;
}
ans += n - l + 1;
ans += l > i ? 1 : 0;
}
System.out.println(ans);
}
}
import sys
input = sys.stdin.readline
n = int(input())
INF = int(2e9)
a = [0] + list(map(int, input().split())) + [INF]
# 预处理连续递增长度
p = [0] * (n + 1)
s = [0] * (n + 2)
for i in range(1, n + 1):
if a[i] >= a[i - 1]:
p[i] = p[i - 1] + 1
else:
p[i] = 1
for i in range(n, 0, -1):
if a[i] <= a[i + 1]:
s[i] = s[i + 1] + 1
else:
s[i] = 1
ans = 0
for i in range(1, n + 1):
x = a[i - 1]
if p[i - 1] < i - 1:
break
# 二分查找右端点
l, r = i, n + 1
while l < r:
mid = (l + r) >> 1
if s[mid] == (n - mid + 1) and x <= a[mid]:
r = mid
else:
l = mid + 1
ans += n - l + 1
ans += l > i
print(ans)
算法及复杂度
- 算法:前缀预处理 + 二分查找
- 时间复杂度: O ( n log n ) \mathcal{O}(n \log n) O(nlogn) - 预处理 O(n),每个左端点二分查找 O(log n)
- 空间复杂度: O ( n ) \mathcal{O}(n) O(n) - 需要存储预处理数组
校招算法笔面试真题解析
1437

被折叠的 条评论
为什么被折叠?



