题解:洛谷 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\) 当前血量)。求最少攻击次数使血量恰好为 \(0\),或判断无法击败。这是一个贪心策略 + 质数判定问题,核心在于优先使用低伤害物理攻击,在血量恰好为质数时用魔法攻击一击必杀。

  2. 算法选择

    • 贪心物理攻击:优先使用物理攻击(伤害 \(1, 2, 4, 8, \ldots\)),保留高伤害灵活性
    • 埃氏筛预处理:预先生成 \(10^5\) 以内质数表,\(O(1)\) 判定质数
    • 魔法攻击时机:血量恰好为质数时立即使用,一击清零
  3. 关键步骤

    • 预处理:埃氏筛生成 is_prime[1..100000]
    • 处理每组数据
      • tmp = 1(当前物理伤害),ans = 0
      • 循环攻击
        • is_prime[x]ans++break(魔法攻击)
        • 否则:x -= tmp(物理攻击),ans++
        • x <= 0x < 0ans = -1break
        • tmp *= 2
    • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(MAX \log \log MAX + t \cdot \log h)\)
    • 空间复杂度:\(O(MAX)\)
  5. 贪心 + 质数判定的核心思想

    • 物理攻击递增策略:低伤害物理攻击优先,使血量逐步下降,增加遇到质数血量的机会
    • 魔法攻击的最优性:只能使用一次,必须在血量恰好为质数时使用才能一击清零。若血量非质数,魔法攻击后剩余血量非零,还需继续攻击,不优
    • 无法击败判定:若物理攻击后血量 \(< 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
posted @ 2026-07-17 14:44  团爸讲算法  阅读(36)  评论(0)    收藏  举报