题目链接
题目大意
给你(n)个人,每个人有(a_i)点攻击力,你需要选(1)个人去击杀敌人,剩下的人守卫。当选中的人的攻击力大于等于敌人的防御力(x)且剩下的人攻击力之和大于等于敌人的攻击力(y),则算成功。你可以(1)枚金币提高任何一个人的攻击力,问你最小的花费是多少。
题目分析
给定一个敌人((防御力,攻击力)=(x,y)),令派出的人的攻击力是(k),所有人的攻击力之和为(sum),那么花费的金币数为: (max(0, x - k) + max(0, y - (sum - k)))。之后对每个人的能力进行枚举,显然这么做会超时。
先对(n)个人的攻击力进行排序,进行去重操作,因为存在攻击力相同的情况,没有必要重复判断。
之后,二分找到第一个大于等于(x)的元素,那么之后的元素就没有必要枚举,因为金币的花费会越来越多。之后我们从找到的元素再往前枚举,此时(max(0, x - k))会逐渐增大,(会逐渐max(0, y - (sum - k)))减小,总的趋势是金币数先小后大,所以我们只需要向前枚举,找到金币的极小点即可。
AC代码
#include <bits/stdc++.h>
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
using namespace std;
constexpr int N = 2e5 + 100;
typedef long long LL;
int n, m;
LL a[N], sum;
inline LL cost(LL x, LL y)
{
LL res = LONG_LONG_MAX;
int k = lower_bound(a, a + n, x) - a;
if (k == n) k --;
for (int i = k; i; i--)
{
LL t = max(0LL, x - a[i]) + max(0LL, y - sum + a[i]);
if (res > t) res = t;
else break;
}
return res;
}
int main()
{
// 神奇,本题比用scanf快好几倍
io;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], sum += a[i];
sort(a + 1, a + n + 1);
n = unique(a + 1, a + n + 1) - a;
cin >> m;
while (m --)
{
LL x, y; cin >> x >> y;
cout << cost(x, y) << '
';
}
return 0;
}