#P2361. 第3题-对战
-
1000ms
Tried: 73
Accepted: 15
Difficulty: 8
所属公司 :
华为
时间 :2023年9月23日-国内
-
算法标签>二分算法
第3题-对战
题面描述
在一个虚拟游戏中,塔子哥的目标是将初始等级x提升到更高的等级y(y > x),与n个对手对战。每当他的等级大于或等于对手的等级时,他就获胜并提升1级,反之则降低1级。塔子哥必须选择与对战次数最少的对手进行比赛。任务是计算他达到目标等级y所需的最少对战次数,若无法实现则输出-1。输入包括n、x、y和n个对手的等级,输出为最少对战数。
思路:二分查找 + 贪心
首先,将所有对手的等级进行排序,并计算每个等级与其索引之间的差值。这个差值表示塔子哥在达到这个等级之前需要赢得的比赛数量。
然后,使用二分查找来找到满足条件的最小比赛次数。在每一步,检查当前的比赛次数是否能够使塔子哥的等级达到目标等级。如果可以,就更新答案,并继续在更小的比赛次数中查找。否则,在更大的比赛次数中查找。
在检查当前的比赛次数是否满足条件时,使用贪心的思想。从最低的等级开始,每次选择能够使塔子哥的等级最大提升的比赛。如果当前的比赛次数不足以提升到下一个等级,就停止查找。
最后,输出所需的最少比赛次数。
具体实现
-
对手等级排序:首先,将所有对手的等级进行排序。这样可以确保塔子哥每次都能以最小的代价与等级较低的对手对战。
-
计算差值:对于每个对手的等级,我们计算塔子哥当前等级与其等级之间的差值。这个差值代表了塔子哥在达到该等级之前需要赢得的比赛数量。通过这个差值,可以判断塔子哥是否能够在规定的比赛次数内提升自己的等级。
-
使用二分查找:通过二分查找找到最小的比赛次数。在每一步,检查当前的比赛次数是否能够使塔子哥的等级达到目标等级。如果可以,就更新答案,并继续在更小的比赛次数中查找;否则,转向更大的比赛次数。
-
贪心策略:在检查比赛次数是否满足条件时,使用贪心的思路。从等级最低的对手开始,每次选择能够使塔子哥的等级最大提升的比赛。这样可以快速累积塔子哥的等级。
-
输出结果:最后,输出所需的最少比赛次数。如果塔子哥无法达到目标等级,则输出-1。
时间复杂度
O(nlogn)
代码
C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n, cnt = 0; // n是对手数量,cnt用来统计可以直接战胜的对手数量
long long x, y; // x是塔子哥的初始等级,y是目标等级
vector<long long> nums; // 存储对手的等级
cin >> n >> x >> y; // 输入对手数量、初始等级和目标等级
for (int i = 0; i < n; i++) {
long long num; // 临时变量,用于读取每个对手的等级
cin >> num;
nums.push_back(num); // 将每个对手的等级添加到数组中
}
sort(nums.begin(), nums.end()); // 对对手等级进行排序
vector<long long> diff(n); // 存储每个对手等级与塔子哥等级之间的差值
for (int i = 0; i < n; i++) {
diff[i] = max(0LL, nums[i] - i); // 计算当前等级差值
if (diff[i] <= x) cnt++; // 如果塔子哥能战胜该对手,计数
}
// 如果当前等级加上可以直接战胜的对手数量 >= 目标等级
if (x + cnt >= y) {
cout << y - x << endl; // 输出所需比赛次数
return 0;
}
// 如果当前可以战胜的对手数量小于无法战胜的对手数量,输出-1
if (n - cnt >= cnt) {
cout << -1 << endl;
return 0;
}
long long ans = 0; // 记录总比赛次数
while (x < y) {
int l = 0, r = n - 1; // 二分查找的左右边界
while (l < r) {
int mid = (l + r + 1) / 2; // 计算中间值
if (diff[mid] <= x) l = mid; // 如果当前等级足够,移动左边界
else r = mid - 1; // 否则,移动右边界
}
if (r == n - 1) { // 如果r指向最后一个元素
cout << ans + y - x << endl; // 输出总比赛次数
return 0;
}
r++; // r指向可以战胜的下一个对手
int cur = 2 * r - n; // 当前能带来的增益
long long next = min(diff[r], y); // 计算下一个目标等级
long long d = next - x; // 计算所需的增益
// 如果能达到目标等级,且所需的比赛次数 <= 当前对手数
if (next == y && d <= r) {
cout << ans + d << endl; // 输出总比赛次数
return 0;
}
// 如果当前可用的增益为负,说明无法提升等级,输出-1
if (cur < 0) {
cout << -1 << endl;
return 0;
}
// 计算所需的比赛次数
long long time = d / cur;
ans += time * n; // 更新总比赛次数
x += cur * time; // 更新塔子哥的等级
}
cout << ans << endl; // 输出最终的比赛次数
return 0;
}
python代码
import bisect # 导入bisect模块,用于二分查找
# 读取输入并将n、x、y分别赋值,x是初始等级,y是目标等级
[n, x, y] = map(int, input().split())
# 读取对手的等级,排序后存入列表a
a = list(sorted(map(int, input().split())))
# 计算每个对手的等级与其在数组中的索引之间的差值
for i in range(n):
a[i] = max(0, a[i] - i) # 将等级减去其索引,确保不小于0
count = 0 # 初始化比赛次数计数
index = 0 # 用于存储当前二分查找的索引
# 只要塔子哥的等级未达到目标等级y,就不断进行循环
while x < y:
# 使用二分查找找到第一个大于x的对手等级的索引
index = bisect.bisect(a, x)
# 如果索引等于n,说明所有对手等级都小于等于x
if index == n:
count += y - x # 直接增加到目标等级所需的比赛次数
break # 结束循环
base = 2 * index - n # 计算当前能够提升的基础增益
target = min(a[index], y) # 当前目标等级应为a[index]或y的最小值
diff = target - x # 计算到达目标等级所需的差值
# 如果目标等级是y,并且差值小于等于当前可用的对手数量
if target == y and diff <= index:
count += diff # 直接增加所需的比赛次数
break # 结束循环
# 如果当前的基础增益为负,表示无法进行有效提升
if base < 0:
count = -1 # 设置为-1,表示无法达成目标
break # 结束循环
# 计算需要的比赛次数,确保整数除法
t = diff // base
count += t * n # 增加总比赛次数
x += base * t # 更新塔子哥的等级
# 输出总的比赛次数
print(count)
Java代码
import java.util.*; // 导入Java的集合类和工具类
public class Main {
public static void main(String[] args) {
int n, cnt = 0; // n为对手数量,cnt为可以直接战胜的对手计数
long x, y; // x为塔子哥的初始等级,y为目标等级
long[] nums; // 存储对手等级的数组
Scanner in = new Scanner(System.in); // 创建输入扫描器
n = in.nextInt(); // 读取对手数量
x = in.nextLong(); // 读取塔子哥的初始等级
y = in.nextLong(); // 读取塔子哥的目标等级
nums = new long[n]; // 初始化对手等级数组
for (int i = 0; i < n; i++) {
nums[i] = in.nextLong(); // 读取每个对手的等级
}
Arrays.sort(nums); // 对对手等级进行排序
long[] diff = new long[n]; // 用于存储每个对手等级与塔子哥等级之间的差值
for (int i = 0; i < n; i++) {
diff[i] = Math.max(0, nums[i] - i); // 计算当前等级差值
if (diff[i] <= x) cnt++; // 如果塔子哥可以战胜该对手,计数
}
// 如果当前等级加上可以直接战胜的对手数量 >= 目标等级
if (x + cnt >= y) {
System.out.println(y - x); // 输出所需比赛次数
return; // 结束程序
}
// 如果当前可战胜的对手数量小于无法战胜的对手数量,输出-1
if (n - cnt >= cnt) {
System.out.println(-1); // 表示无法达到目标等级
return; // 结束程序
}
long ans = 0; // 初始化比赛次数计数
while (x < y) { // 当塔子哥的等级小于目标等级时继续循环
int l = 0, r = n - 1; // 二分查找的左右边界
while (l < r) { // 进行二分查找
int mid = (l + r + 1) >> 1; // 计算中间值
if (diff[mid] <= x) l = mid; // 如果当前等级足够,移动左边界
else r = mid - 1; // 否则,移动右边界
}
// 如果r指向最后一个元素,说明塔子哥已经能够战胜所有对手
if (r == n - 1) {
System.out.println(ans + y - x); // 输出总比赛次数
return; // 结束程序
}
r++; // r指向可以战胜的下一个对手
int cur = 2 * r - n; // 当前能带来的增益
long next = Math.min(diff[r], y); // 计算下一个目标等级
long d = next - x; // 计算所需的增益
// 如果能达到目标等级,且所需的比赛次数 <= 当前对手数
if (next == y && d <= r) {
System.out.println(ans + d); // 输出总比赛次数
return; // 结束程序
}
// 如果当前可用的增益为负,说明无法提升等级,输出-1
if (cur < 0) {
System.out.println(-1); // 表示无法达成目标
return; // 结束程序
}
// 计算所需的比赛次数
long time = d / cur; // 计算可以进行的比赛次数
ans += time * n; // 更新总比赛次数
x += cur * time; // 更新塔子哥的等级
}
System.out.println(ans); // 输出最终的比赛次数
}
}
题目描述
在一个虚拟的游戏世界中,小明参加了一场在线比赛,这场比赛要求他与n个对手进行对战,以提高自己的等级。然而,比赛有着一些独特的规则和挑战。
小明的目标很明确:他希望将自己的等级从初始等级x提升到更高的等级y(y > x)。
在比赛中,如果小明的等级大于等于对手的等级,他就能够获胜,并提升1级。但如果小明输掉了比赛,他将减少1级。对手的等级始终保持不变,不受比赛结果的影响。
比赛有一条特殊规则:每局比赛小明只能选择和对战次数最少的对手进行对战。
小明面临的挑战是,在保证按照规则选择对手的情况下,以尽可能少的比赛次数提升自己的等级。他需要计算出实现这一目标所需的最少比赛次数,以达到他渴望的更高等级y。
输入格式
第一行包含三个整数n,x和y(1≤n≤2×105,1≤x<y≤1012)表示对手个数,小明的初始等级和期望等级。
第二行包含n个整数a1,a2,a3,...,an(1≤ai≤1012)表示对手的等级。
输出格式
输出小明提升到等级y所需的最少对战数,如果不可能达到目标,则输出−1。
7 2 10
3 1 9 2 5 20 8
20