题解:洛谷 B4050 挑战怪物
【题目来源】
洛谷:B4050 [GESP202409 五级] 挑战怪物 - 洛谷
【题目描述】
小杨正在和一个怪物战斗,怪物的血量为\(h\),只有当怪物的血量恰好为\(0\)时小杨才能够成功击败怪物。
小杨有两种攻击怪物的方式:
- 物理攻击。假设当前为小杨第\(i\)次使用物理攻击,则会对怪物造成\(2^{i-1}\)点伤害。
- 魔法攻击。小杨选择任意一个质数\(x\)(\(x\)不能超过怪物当前血量),对怪物造成 \(x\) 点伤害。由于小杨并不擅长魔法,他只能使用至多一次魔法攻击。
小杨想知道自己能否击败怪物,如果能,小杨想知道自己最少需要多少次攻击。
【输入】
第一行包含一个正整数\(t\),代表测试用例组数。
接下来是\(t\)组测试用例。对于每组测试用例,第一行包含一个正整数\(h\),代表怪物血量。
【输出】
对于每组测试用例,如果小杨能够击败怪物,输出一个整数,代表小杨需要的最少攻击次数,如果不能击败怪物,输出\(-1\)。
【输入样例】
3
6
188
9999
【输出样例】
2
4
-1
【核心思想】
-
问题分析:怪物血量为 \(h\),小杨有物理攻击(第 \(i\) 次伤害为 \(2^{i-1}\))和至多一次魔法攻击(选择质数 \(x \leq\) 当前血量造成 \(x\) 点伤害)。求最少攻击次数使血量恰好为 \(0\),或判断无法击败。这是一个贪心策略 + 质数判定问题,核心在于分析物理攻击的累积效果与魔法攻击的最佳使用时机。
-
算法选择:
- 贪心物理攻击:优先使用物理攻击,因为物理攻击伤害递增,尽早使用低伤害攻击保留灵活性
- 埃氏筛预处理:预先生成 \(10^5\) 以内的质数表,\(O(1)\) 判定质数
- 魔法攻击的最优使用:在血量恰好为质数时使用魔法攻击一击必杀,或在物理攻击后血量变为质数时使用
-
关键步骤:
- 预处理:埃氏筛生成质数表 \(is\_prime[1..100000]\)
- 处理每组测试用例:
- \(tmp \leftarrow 1\)(当前物理攻击伤害),\(ans \leftarrow 0\)
- 循环攻击:
- 若当前血量 \(x\) 为质数:\(ans \leftarrow ans + 1\),\(break\)(魔法攻击一击必杀)
- 否则:\(x \leftarrow x - tmp\)(物理攻击),\(ans \leftarrow ans + 1\)
- 若 \(x \leq 0\):若 \(x < 0\) 则 \(ans \leftarrow -1\),\(break\)
- \(tmp \leftarrow tmp \times 2\)
- 输出结果:\(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(MAX \log \log MAX + t \cdot \log h)\),埃氏筛 \(O(MAX \log \log MAX)\),每组测试用例物理攻击次数为 \(O(\log h)\)
- 空间复杂度:\(O(MAX)\),质数标记数组
-
贪心策略与质数判定的核心思想:
- 物理攻击的累积性:第 \(i\) 次物理攻击伤害为 \(2^{i-1}\),前 \(k\) 次物理攻击总伤害为 \(2^k - 1\)。物理攻击伤害指数增长,因此攻击次数很少即可造成大量伤害
- 魔法攻击的最优时机:魔法攻击只能使用一次且必须选择质数。最优策略是在血量恰好为质数时使用,一击清零。若当前血量非质数,先用物理攻击降低血量,再判断是否为质数
- 贪心正确性:优先使用低伤害物理攻击(\(1, 2, 4, 8, \ldots\)),保留高伤害物理攻击的灵活性,使得血量更快接近质数或 \(0\)
- 无法击败的判定:若物理攻击后血量 \(< 0\) 且从未遇到质数血量,则无法恰好清零,输出 \(-1\)
- 适用于组合攻击策略优化、质数判定与贪心选择类问题
【算法标签】
普及 #质数
【代码详解】
#include <bits/stdc++.h>
using namespace std;
vector<int> prime; // 用于存储所有素数
bool is_prime[100010]; // 标记数组,用于判断一个数是否为素数
// 埃氏筛法,生成从2到n的所有素数
void Eratosthenes(int n) {
memset(is_prime, true, sizeof(is_prime)); // 初始化标记数组,全设为true(假定全是素数)
is_prime[0] = is_prime[1] = false; // 0和1不是素数
for (int i=2; i<=n; i++) {
if (is_prime[i]) { // 如果当前数字是素数
prime.push_back(i); // 将其加入素数列表
for (int j=2*i; j<=n; j+=i) // 将该素数的倍数全部标记为非素数
is_prime[j] = false;
}
}
}
int main()
{
Eratosthenes(100000); // 生成最多到100000的所有素数
int t; // 测试用例数量
cin >> t;
while (t--) {
int tmp = 1; // 初始攻击力
int x; // 怪物的血量
cin >> x;
int ans = 0; // 攻击次数
while (true) {
if (is_prime[x]) { // 如果当前血量是素数
ans++; // 增加一次攻击
break; // 结束循环
}
x -= tmp; // 使用物理攻击,减少血量
ans++; // 增加一次攻击次数
if (x<=0) { // 如果血量小于等于0
if (x<0) ans = -1; // 如果血量小于0,设置结果为-1表示失败
break; // 结束循环
}
tmp *= 2; // 每次攻击后,攻击力翻倍
}
cout << ans << endl; // 输出结果
}
return 0;
}
【运行结果】
3
6
2
188
4
9999
-1
浙公网安备 33010602011771号