首页 > 试题广场 >

消灭怪物

[编程题]消灭怪物
  • 热度指数:1590 时间限制:C/C++ 3秒,其他语言6秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
现在有一个打怪类型的游戏,这个游戏是这样的,你有个技能,每一个技能会有一个伤害,同时若怪物低于一定的血量,则该技能可能造成双倍伤害,每一个技能最多只能释放一次,已知怪物有点血量,现在想问你最少用几个技能能消灭掉他(血量小于等于)。

输入描述:

第一行输入一个整数,代表有组测试数据。

对于每一组测试数据,一行输入个整数,代表有个技能,怪物有点血量。

接下来行,每一行输入两个数,代表该技能造成的伤害和怪物血量小于等于的时候该技能会造成双倍伤害







输出描述:
对于每一组数据,输出一行,代表使用的最少技能数量,若无法消灭输出-1。
示例1

输入

3
3 100
10 20
45 89
5 40
3 100
10 20
45 90
5 40
3 100
10 20
45 84
5 40

输出

3
2
-1

说明

总共3组数据
对于第一组:我们首先用技能1,此时怪物血量剩余90,然后使用技能3,此时怪物剩余血量为85,最后用技能2,由于技能2在怪物血量小于89的时候双倍伤害,故此时怪物已经消灭,答案为3
对于第二组:我们首先用技能1,此时怪物血量剩余90,然后用技能2,由于技能2在怪物血量小于90的时候双倍伤害,故此时怪物已经消灭,答案为2
对于第三组:我们首先用技能1,此时怪物血量剩余90,然后使用技能3,此时怪物剩余血量为85,最后用技能2,由于技能2在怪物血量小于84的时候双倍伤害,故此时怪物无法消灭,输出-1
回溯算法,一开始没想到,可恶。
#include "bits/stdc++.h"
using namespace std;

int ans=INT_MAX;
vector<vector<int>> Ax;


void backtracking(int step,int Hp){
    if(Hp<=0){
        ans=min(ans,step);
        return  ;
    }
    for(int i=0;i<Ax.size();++i){
        if(Ax[i][2]){
            Ax[i][2]=0;    //使用过了,为0
            if(Hp<=Ax[i][1]){
                backtracking(step+1,  Hp-2*Ax[i][0]);
            }
            else{
                backtracking(step+1,  Hp-Ax[i][0]);
            }
            Ax[i][2]=1;    //回溯,恢复未使用状态
        }
    }
}


int main() {
    int T;
    
    cin >> T ;
    while (T) { // 注意 while 处理多个 case
        int n,m;
        cin >>n>>m;
        if(m==0){
            cout<<0<<endl;
            break;
        }
        // 分别为每个技能对应的A,x以及使用标记1
        Ax=vector<vector<int>>(n,vector<int>(3,1));
        for(int i=0;i<n;++i){
            cin>>Ax[i][0]>>Ax[i][1];
        }
        
        backtracking(0,m);
        if(ans>n)cout<<-1<<endl;
        else cout<<ans<<endl;
        T--;
        ans=INT_MAX;
        }
        
    return 0;
}


发表于 2023-09-11 22:47:47 回复(0)
python3
#利用回溯解决,但是使用python时容易超时,所以增加了剪枝
t = int(input())
for _ in range(t):
    #n个技能,m点血量
    n,m = list(map(int,input().split()))
    tem = [] #存储每个技能的伤害
    #tem.sort(key=lambda x:(-x[1],-x[0]))
    
    for _ in range(n):
        tem.append(tuple(map(int,input().split())))
    
    length = len(tem)
    ans = -1
    def dfs(blood,visit=set()):
        global ans
        
        #假设剩余技能全部打出二倍伤害
        rest_max_sum = sum([2*tem[i][0] for i in range(length) if i not in visit])
        if blood > rest_max_sum: return #剪枝

        if ans != -1 and len(visit) >= ans: return #剪枝

        if len(visit) == length and blood > 0:
            return 
        if blood <= 0:
            ans = min(ans,len(visit)) if ans >= 0 else len(visit)
        for ind,(x,y) in enumerate(tem):
            if ind not in visit:
                visit.add(ind)
                if blood <= y:
                    dfs(blood-2*x,visit)
                else:
                    dfs(blood-x,visit)
                visit.remove(ind)
    dfs(m)
    print(ans)


编辑于 2023-07-31 21:14:10 回复(0)
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 15;

int n, m;
int a[N], b[N];
bool st[N];
int res = N;

void dfs(int u, int hp, int cnt)
{
	if(hp <= 0) {
		res = min(res, cnt);
		return ;
	}
	
	if(u == n + 1) return ;
	
	for(int i = 1; i <= n; i ++)
	{
		if(!st[i])
		{
			st[i] = true;
			if(hp <= b[i]) dfs(u + 1, hp - a[i] * 2, cnt + 1);
			else dfs(u + 1, hp - a[i], cnt + 1);
			st[i] = false;
		}
	}
}

int main()
{
	int T;
	cin >> T;
	while(T --)
	{
		cin >> n >> m;
		for(int i = 1; i <= n; i ++) cin >> a[i] >> b[i];
		
		res = N;
		memset(st, false, sizeof st);
		dfs(0, m, 0);

		if(res == N) puts("-1");
		else cout << res << endl;
	}

	return 0;
}

发表于 2023-01-12 12:41:04 回复(2)
技能只有10!!!全排列的代码就可以ac
只是全排列不用记录路径 记录一下monster剩余血量就ok
#include<iostream>

using namespace std;
const int N = 11,M = 1e6 + 3;
int kill[N],blood[M];
bool used[N];
// n:技能个数   st:当前来到第st rest:怪兽目前剩余血量
int f(int n,int st,int rest,bool used[])
{
    //monster死了 用了st-1个技能 
    if(rest <= 0) return st - 1;
    //技能用完了 monster没死  返回最大值 代表无效
    if(st == n + 1) return 0x3f3f3f3f;

    int ans = 0x3f3f3f3f;
    for(int i = 1; i <= n; ++ i)
    {
        if(used[i] == true) continue;
        used[i] = true;
        ans = min(ans,f(n,st + 1,rest-(rest > blood[i] ? kill[i] : kill[i] * 2),used));
        used[i] = false;
    }
    return ans;
} 

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);

    int _;cin >> _;
    while(_ --)
    {
        int n,m;cin >> n >> m;
        for(int i = 1;i <= n; ++ i)
        {
            int k,b;cin >> k >> b;
            kill[i] = k;
            blood[i] = b;
        }
        for(int i = 1;i <= N; ++ i) used[i] = false;
        int ans = f(n,1,m,used);
        if(ans == 0x3f3f3f3f) cout << -1 << '\n';
        else cout << ans << '\n';
    }
    return 0;
}



发表于 2024-08-02 12:11:11 回复(0)
#include<bits/stdc++.h>
using namespace std;

vector<int> op; // operations
vector<int> bl; // blood_limit
int blood;      // sum of blood

class Slay_Monster
{
private:
    void my_swap(int i , int j)
    {
        int temp1 = op[i];
        op[i] = op[j];
        op[j] = temp1;

        int temp2 = bl[i];
        bl[i] = bl[j];
        bl[j] = temp2;

        return;
    }
private:
    // 函数功能:返回杀死怪兽 所使用的 最少的 技能数
    // 函数结束标志:怪物被杀死 blood <= 0
    // 假设有3个技能
    int f(int i)
    {  
        // 怪兽被杀死:
        if(blood <= 0)
        {
            return i;
        }
        else
        {
            // 怪兽没被杀死:
            // 1)技能数用完了
            if(i==op.size())
            {
                return INT_MAX; // 返回整数最大值表示:这一连招不能将怪兽杀死
            }
            // 2)技能还没有用完:
            else
            {  
                int ans=INT_MAX; // 使用的最少的技能数
               
                // eg: 3个技能,已经放了一个
                // 0   1 2
                // a | b c
                //     i            i:还能使用哪些技能
                //     j            j:使用哪个技能
                // 分类讨论:用回溯的方法一个一个试:
                for(int j=i ; j<op.size() ; j++)
                {
                    // 决定使用 j 技能:因为后序技能的释放受之前决策的影响,修改 op&bl 的排列顺序
                    my_swap(i , j);
                    // 使用 j技能,但是 j技能被换到 i技能位置上 ==> 使用 i技能 :
                    int blood_back = blood; // 血液的状态回溯
                    blood -= blood<=(bl[i]) ? (2*op[i]) : (op[i]);
                    ans = min(ans , f(i+1)); // 如果后续连招不能杀死怪物,返回 INT_MAX

                    // 将 op&bl 的顺序修改回来,进行下一种技能的选择
                    my_swap(i , j);
                    blood = blood_back;
                }

                return ans; // 返回所用的最少的技能数
            }
        }

    }
public:
    int Min_Op()
    {
        return f(0)==INT_MAX ? -1 : f(0); // 从第 0 个技能开始考虑
    }
};

int main()
{
    int n; // n组测试数据
    scanf("%d" , &n);

    // eg:
    // 3 100
    // 10 20
    // 45 89
    // 5 40
    while(n--)
    {
        // 更新:
        op.clear();
        bl.clear();

        // 输入数据:
        int m=0; // 技能数
        scanf("%d" , &m); // 技能数
        scanf("%d" , &blood); // 怪兽血量
        for(int i=0 ; i<m ; i++) // 技能 | 血量阈值 详细参数
        {
            int x=0; // 技能伤害
            int y=0; // 血量阈值
            scanf("%d%d" , &x , &y);
            op.push_back(x);
            bl.push_back(y);
        }

        // 计算杀死怪兽的最少技能数:
        Slay_Monster sm;
        printf("%d\n" , sm.Min_Op());
    }

    return 0;
}
发表于 2024-04-03 13:18:05 回复(0)

Python 状态压缩+记忆化搜索

dfs(health,m)代表剩下生命值是health,剩下技能集合是m情况下,把怪物杀死所用技能最少的数量。因为有@cache缓存状态,所以不会重复计算,很快。
初始集合u=(1<<n)-1(全1)

from math import inf
from functools import cache

def minSkill(l: list, m: int) -> int:
    n = len(l)
    u = (1 << n) - 1

    @cache
    def dfs(health: int, ss: int) -> int:
        if health <= 0:
            return 0
        if ss == 0:
            return inf

        ans = inf
        for i in range(n):
            if (ss >> i) & 1:
                A, x = l[i]
                if health <= x:
                    ans = min(ans, dfs(health - 2 * A, ss ^ (1 << i)) + 1)
                else:
                    ans = min(ans, dfs(health - A, ss ^ (1 << i)) + 1)
        return ans

    t = dfs(m, u)
    return t if t != inf else -1


T = int(input())

for _ in range(T):
    n, m = map(int, input().split())
    l = []
    for _ in range(n):
        A, x = map(int, input().split())
        l.append([A, x])
    print(minSkill(l, m))
发表于 2023-09-18 10:38:55 回复(0)
// 暴力回溯法
#include <climits>
#include <iostream>
#include <utility>
#include <vector>
#include "bits/stdc++.h"
using namespace std;

int imin = INT_MAX;
int blood;
bool flag;
void recur(vector<pair<int, int>>& arr, int index) {
    if(index == arr.size() - 1)
    {
        // 最小技能次数
        int tmp = blood;
        int count = 0;
        for(int i = 0; i < arr.size(); i++) {
            if(tmp <= arr[i].second) {
                tmp -= arr[i].first * 2;
            }else {
                tmp -= arr[i].first;
            }
            count++;
            if(tmp <= 0) {
                flag = true;
                imin = min(count, imin);
                break;
            }
        }
    }
    for(int i = index; i < arr.size(); i++) {
        swap(arr[index], arr[i]);
        recur(arr, index+1);
        swap(arr[index], arr[i]);
    }
}

int main() {
    int n;
    cin >> n;
    while (n--) {
        int a, b;
        int t1, t2;
        cin >> a >> blood;
        vector<pair<int, int>> arr;
        for (int i = 0; i < a; i++) {
            cin >> t1 >> t2;
            arr.emplace_back(make_pair(t1, t2));
        }
        flag = false;
        recur(arr, 0);

        cout << ((flag == true) ? imin : -1) << endl;
        imin = INT_MAX;
    }
    return 0;
}

发表于 2023-06-07 13:01:23 回复(0)
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * @author qxm
 * @create 2023-02-27 22:12
 **/
//回溯问题的排列问题
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        for (int i = 0; i < t; i++) {
            int n = sc.nextInt();
            int m = sc.nextInt();
            int[][] jn = new int[n][2];
            for (int j = 0; j < n; j++) {
                jn[j][0] = sc.nextInt();
                jn[j][1] = sc.nextInt();
            }
            Main obj = new Main();
            obj.blood = m;
            boolean[] isUsed = new boolean[n];
            obj.backtracking(jn, isUsed);
            if (obj.min == Integer.MAX_VALUE) {
                System.out.println(-1);
            } else {
                System.out.println(obj.min);
            }
        }
    }

    int min = Integer.MAX_VALUE;
    List<Integer> path = new ArrayList<>();
    int blood = 0;

    public void backtracking(int[][] jn, boolean[] isUsed) {
        if (blood <= 0) {
            min = Math.min(min, path.size());
            return;
        }
        for (int i = 0; i < jn.length; i++) {
            if (isUsed[i]) {
                continue;
            }
            if (blood <= jn[i][1]) {
                blood -= jn[i][0] * 2;
                path.add(jn[i][0]);
                isUsed[i] = true;
                backtracking(jn, isUsed);
                isUsed[i] = false;
                path.remove(path.size() - 1);
                blood += jn[i][0] * 2;
            } else {
                blood -= jn[i][0];
                path.add(jn[i][0]);
                isUsed[i] = true;
                backtracking(jn, isUsed);
                isUsed[i] = false;
                path.remove(path.size() - 1);
                blood += jn[i][0];
            }
        }
    }
}

发表于 2023-02-27 22:34:47 回复(0)
应该是小于等于的时候双倍伤害,描述有点问题
import java.util.Scanner;
import java.util.Stack;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int size = in.nextInt();// 用例数
        int index = 0;
        while (in.hasNextInt()) {
            int skillNum = in.nextInt();
            int blood = in.nextInt();
            int[][] skills = new int[skillNum][2];
            
            for (int i = 0; i < skillNum; i++) {
                skills[i][0] = in.nextInt();
                skills[i][1] = in.nextInt();
            }
            minKilled(skills, blood);
        }
 
    }
    
    public static void minKilled(int[][] skills, int blood) {
        // skills[i] :技能伤害 小于特定值则造成双倍伤害
        int ans = Integer.MAX_VALUE;
        for (int i = 0; i < skills.length; i++) {
            boolean[] used = new boolean[skills.length];
            used[i] = true;
            ans = Math.min(ans, dfs(skills, i, blood, used, 0));
            used[i] = false;
        }
        System.out.println(ans == Integer.MAX_VALUE ? -1 : ans);
    }
    
        private static int dfs(int[][] skills, int pos, int restBlood, boolean[] used, int usedSkillNum) {
        if (restBlood <= 0) {
            return 0;
        }

        if (usedSkillNum > skills.length) {
            return Integer.MAX_VALUE;
        }

        int ans = Integer.MAX_VALUE;
        int rest = restBlood - (restBlood <= skills[pos][1] ? 2 * skills[pos][0] : skills[pos][0]);
        if (rest <= 0) {
            return 1;
        }
        for (int i = 0; i < skills.length; i++) {
            if (i != pos && !used[i]) {
                used[i] = true;
                int dfs = dfs(skills, i, rest, used, usedSkillNum + 1);
                if (dfs != Integer.MAX_VALUE) {
                    ans = Math.min(ans, 1 + dfs);
                }
                used[i] = false;
            }
        }
        return ans;
    }
}

发表于 2023-01-17 17:46:42 回复(0)
import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息

public class Main {
    public static int res;  //全局变量,可以在回溯的过程中不考虑返回值

    public static void backTracking(int[]axe, int[] cmp, int state, int m, int step) {
        if (m <= 0) {
            //血量小于0收割结果
            res = Math.min(step, res);
            return;
        }
        if (state == 0) return;
        for (int i = 0; i < axe.length; i++) {
            if (((state>>i) & 1) != 1) continue;
            int t = m - axe[i];
            if (m <= cmp[i]) t = t - axe[i]; 
            //继续向下递归
            backTracking(axe, cmp, state^(1<<i), t, step+1);
        }
        return;
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        int T = in.nextInt();
        for (int i = 0; i < T; i++) {
            res = Integer.MAX_VALUE;
            int n = in.nextInt();
            int m = in.nextInt();
            int[] axe = new int[n];
            int[] cmp = new int[n]; 
            for (int j = 0; j < n; j++) {
                axe[j] = in.nextInt();
                cmp[j] = in.nextInt();
            } 
            int state = (1<<(n)) - 1;
            backTracking(axe, cmp, state, m, 0);
            if (res == Integer.MAX_VALUE) res = -1;
            System.out.println(res);
        }
    }
}
用一个状态数组标记已经访问过的state可能耗时会缩短一些。
发表于 2022-12-25 23:13:30 回复(0)
/**
基本思路:
利用回溯法寻找全排列,遍历所有情况存在数组status中,计算最小攻击次数
/*
#include <iostream>
#include <vector>
#include <map>
#include <numeric>
void backtrace(const std::vector<std::pair<int, int>>& ax, std::vector<int>& recorder, int T_i, std::vector<int>& status) {

    if (T_i <=0) {
        status.push_back(std::accumulate(recorder.begin(), recorder.end(), 0));
        return;
    }
    for (size_t i=0; i<ax.size(); i++) {
        if (!recorder[i]) {
            recorder[i] = 1; // 选择
            // if (T_i<=ax[i].second) { // 双倍伤害
            //     T_i -= 2*ax[i].first;
            // } else {
            //     T_i -= ax[i].first; // 直接伤害
            // }

            // backtrace(ax, recorder, T_i, status);
            // recorder[i] = 0; //撤销选择
            // if (T_i<=ax[i].second) { // 双倍伤害   ***此时Ti变了,会导致判断条件发生变化,应该用临时变量代替输入到backtrace里面,或者直接传入结果。
            //     T_i += 2*ax[i].first;
            // } else {
            //     T_i += ax[i].first; // 直接伤害
            // }


            // 此时 递推回来后T_i没变
            if (T_i <= ax[i].second) {
                backtrace(ax, recorder, T_i-2*ax[i].first, status);
            } else {
                backtrace(ax, recorder, T_i-ax[i].first, status);
            }
            recorder[i] = 0;  // 撤销选择
        }
        

        
    }

    // status.push_back(11); // 最多只有10个技能
}
int main(int argc, const char** argv) {

    int T;
    std::cin >> T; // 测试次数

    int m, n;

    int A,x;

    std::vector<std::pair<int, int>> ax;

    std::vector<int> num; // 每次的最小攻击次数
    for (int i=0; i<T; i++) {
        std::cin>>n >> m; // 技能,血量

        while(n--) {
            std::cin>>A>>x;
            ax.push_back({A, x}); // 伤害, 阈值            
        }
        // std::sort(ax.begin(), ax.end(), [](std::pair<int, int> a , std::pair<int, int> b){
        //     return a.second<=b.second;
        // });
        std::vector<int> recorder(ax.size(), 0); // 单次记录是否选择
        std::vector<int> status; // 单次所有的状态
        status.push_back(11); //最多只有10个技能

        backtrace(ax, recorder, m, status);
        int min_n = 11; //最小进攻次数
        for (size_t s=0; s < status.size(); s++) {
            if (min_n > status[s]) {
                min_n = status[s];
            }
        }
        if (min_n==11) num.push_back(-1);
        else num.push_back(min_n);


        ax.clear();
        
    
    }
    for (size_t i=0; i<num.size(); i++) {
        std::cout << num[i] << std::endl;
    }
    return 0;
}

/*
3
3 100
10 20
45 89
5 40
3 100
10 20
45 90
5 40
3 100
10 20
45 84
5 40
*/

发表于 2022-11-21 19:11:53 回复(0)
尝试所有可能的算法但是时间太久,permutation数量随着N增加而快速增多、
import itertools
T=input()
matrix1=[]
matrix2=[]
matrix3=[]
for i in range(int(T)):
    n,m=map(int, input().split())
    for j in range(int(n)):
        A,x=map(int, input().split())
        k=[A,x]
        matrix1.append(k)
    matrix2.append(matrix1)
    matrix3.append([n,m])
    matrix1=[]

def attack(n,m,l):
    list1=[]
    for i in range(1,len(l)+1):
        list1.append(i)
    t = itertools.permutations(list1,n)
    list2=list(t)
    list3=[]
    for i in range(len(list2)):
        blood=m
        numbers=0
        for j in range(n):
            damage=l[list2[i][j]-1][0]
            if blood<=l[list2[i][j]-1][1]:
                damage=damage*2
            blood=blood-damage
            numbers=numbers+1
            if blood<=0:
                list3.append(numbers)
                numbers=0
                break
    if len(list3)==0:
        return -1
    list3.sort()
    return list3[0]

for i in range(len(matrix3)):
    print(attack(matrix3[i][0],matrix3[i][1],matrix2[i]))

发表于 2022-10-13 20:32:11 回复(0)
import java.util.Scanner;

/**
 * @author wudi
 * @since 2022/9/11 - 10:20
   暴力递归 这个标签因为是直接在原来的类上改的 所有日期不对
 */
public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int group = scanner.nextInt();
        while (group > 0){
            int n = scanner.nextInt();
            int m = scanner.nextInt();
            int []attack = new int[n];
            int []doubleAck = new int[n];
            // n 个技能 怪物有m点血量
            boolean []used  = new boolean[n];
            for (int i = 0;i<n;i++){
                attack[i] = scanner.nextInt();
                doubleAck[i] = scanner.nextInt();
            }
            int process = process(attack, doubleAck, m,used);
            System.out.println(process);
            group--;
        }

    }

    private static int process(int[] attack, int[] doubleAck, int m,boolean []used) {
        if (m <= 0) return 0;
        int count = 0x7fffffff;
        for (int i = 0;i<attack.length;i++){
            if (used[i]) continue;
            if(m <= doubleAck[i]){
                used[i] = true;
                int res = process(attack, doubleAck, m - 2 * attack[i], used);
                if (res!=-1){
                    count = Math.min(count,res + 1);
                }
                used[i] = false;
            }else {
                used[i] = true;
                int res = process(attack, doubleAck, m  - attack[i], used);
                if (res!=-1){
                    count = Math.min(count,res + 1);
                }
                used[i] = false;
            }
        }
        return count == 0x7fffffff?-1:count;
    }
}

发表于 2022-10-07 21:56:19 回复(0)

问题信息

上传者:小小
难度:
13条回答 4972浏览

热门推荐

通过挑战的用户

查看代码
消灭怪物