题解:洛谷 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\) 当前血量)。求最少攻击次数使血量恰好为 \(0\),或判断无法击败。这是一个贪心策略 + 质数判定问题,核心在于优先使用低伤害物理攻击,在血量恰好为质数时用魔法攻击一击必杀。
-
算法选择:
- 贪心物理攻击:优先使用物理攻击(伤害 \(1, 2, 4, 8, \ldots\)),保留高伤害灵活性
- 埃氏筛预处理:预先生成 \(10^5\) 以内质数表,\(O(1)\) 判定质数
- 魔法攻击时机:血量恰好为质数时立即使用,一击清零
-
关键步骤:
- 预处理:埃氏筛生成
is_prime[1..100000] - 处理每组数据:
tmp = 1(当前物理伤害),ans = 0- 循环攻击:
- 若
is_prime[x]:ans++,break(魔法攻击) - 否则:
x -= tmp(物理攻击),ans++ - 若
x <= 0:x < 0则ans = -1,break tmp *= 2
- 若
- 输出
ans
- 预处理:埃氏筛生成
-
时间/空间复杂度:
- 时间复杂度:\(O(MAX \log \log MAX + t \cdot \log h)\)
- 空间复杂度:\(O(MAX)\)
-
贪心 + 质数判定的核心思想:
- 物理攻击递增策略:低伤害物理攻击优先,使血量逐步下降,增加遇到质数血量的机会
- 魔法攻击的最优性:只能使用一次,必须在血量恰好为质数时使用才能一击清零。若血量非质数,魔法攻击后剩余血量非零,还需继续攻击,不优
- 无法击败判定:若物理攻击后血量 \(< 0\) 且从未遇到质数,则无法恰好清零
- 适用于"组合攻击 + 特殊条件一击必杀"的贪心策略问题
【算法标签】
普及 #质数
【代码详解】
#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号