题解:洛谷 B4050 挑战怪物

【题目来源】

洛谷:B4050 [GESP202409 五级] 挑战怪物 - 洛谷

【题目描述】

小杨正在和一个怪物战斗,怪物的血量为\(h\),只有当怪物的血量恰好为\(0\)时小杨才能够成功击败怪物。

小杨有两种攻击怪物的方式:

  • 物理攻击。假设当前为小杨第\(i\)次使用物理攻击,则会对怪物造成\(2^{i-1}\)点伤害。
  • 魔法攻击。小杨选择任意一个质数\(x\)\(x\)不能超过怪物当前血量),对怪物造成 \(x\) 点伤害。由于小杨并不擅长魔法,他只能使用至多一次魔法攻击。

小杨想知道自己能否击败怪物,如果能,小杨想知道自己最少需要多少次攻击。

【输入】

第一行包含一个正整数\(t\),代表测试用例组数。

接下来是\(t\)组测试用例。对于每组测试用例,第一行包含一个正整数\(h\),代表怪物血量。

【输出】

对于每组测试用例,如果小杨能够击败怪物,输出一个整数,代表小杨需要的最少攻击次数,如果不能击败怪物,输出\(-1\)

【输入样例】

3
6
188
9999

【输出样例】

2
4
-1

【核心思想】

  1. 问题分析:怪物血量为 \(h\),小杨有物理攻击(第 \(i\) 次伤害为 \(2^{i-1}\))和至多一次魔法攻击(选择质数 \(x \leq\) 当前血量造成 \(x\) 点伤害)。求最少攻击次数使血量恰好为 \(0\),或判断无法击败。这是一个贪心策略 + 质数判定问题,核心在于分析物理攻击的累积效果与魔法攻击的最佳使用时机。

  2. 算法选择

    • 贪心物理攻击:优先使用物理攻击,因为物理攻击伤害递增,尽早使用低伤害攻击保留灵活性
    • 埃氏筛预处理:预先生成 \(10^5\) 以内的质数表,\(O(1)\) 判定质数
    • 魔法攻击的最优使用:在血量恰好为质数时使用魔法攻击一击必杀,或在物理攻击后血量变为质数时使用
  3. 关键步骤

    • 预处理:埃氏筛生成质数表 \(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(MAX \log \log MAX + t \cdot \log h)\),埃氏筛 \(O(MAX \log \log MAX)\),每组测试用例物理攻击次数为 \(O(\log h)\)
    • 空间复杂度:\(O(MAX)\),质数标记数组
  5. 贪心策略与质数判定的核心思想

    • 物理攻击的累积性:第 \(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
posted @ 2026-07-17 14:44  团爸讲算法  阅读(2)  评论(0)    收藏  举报