[二分][状压dp]Boss Rush 2022杭电多校第3场 1002

23 篇文章 0 订阅
2 篇文章 0 订阅

Finally, Little Q gets his weapon at level 10^5105 in the RPG game, now he is trying to beat the boss as soon as possible. The boss has HH units of health point (HP), Little Q needs to cause at least HH units of damage to beat the boss.

Little Q has learnt nn skills, labeled by 1,2,\dots,n1,2,…,n. Each skill can not be used multiple times, because there is not enough time for Little Q to wait for the skill to cool down. Assume Little Q uses the ii-th skill at the xx-th frame, the actor controlled by him will take t_iti​ frames to perform, which means Little Q will not be allowed to use other skills until the (x+t_i)(x+ti​)-th frame. The length of the damage sequence of the ii-th skill is len_ileni​, which means the skill will cause d_{i,j}di,j​ (0\leq j < len_i0≤j<leni​) units of damage at the (x+j)(x+j)-th frame if Little Q uses the ii-th skill at the xx-th frame. Note that len_ileni​ can be greater than t_iti​, for example, the burning skill can burn the boss for a long period, but takes a little time to cast the fire.

The game starts at the 00-th frame. Your task is to help Little Q beat the boss as soon as possible, or determine Little Q can't beat the boss using all the skills at most once.

Input

The first line contains a single integer TT (1 \leq T \leq 1001≤T≤100), the number of test cases. For each test case:

The first line contains two integers nn and HH (1 \leq n \leq 181≤n≤18, 1\leq H\leq 10^{18}1≤H≤1018), denoting the number of skills and the HP of the boss.

For each skill, the first line contains two integers t_iti​ and len_ileni​ (1 \leq t_i,len_i\leq 100\,0001≤ti​,leni​≤100000), the second line contains len_ileni​ integers d_{i,0},d_{i,1},\dots,d_{i,len_i-1}di,0​,di,1​,…,di,leni​−1​ (1\leq d_{i,j}\leq 10^91≤di,j​≤109).

It is guaranteed that the sum of all len_ileni​ is at most 3\,000\,0003000000, and there are at most 55 cases such that n>10n>10.

Output

For each test case, output a single line containing an integer, denoting the first frame to beat the boss. If it is impossible to beat the boss, please print ''\texttt{-1}-1'' instead.

Sample

InputOutput
 
3 
1 100 
5 3 
50 60 70 
2 100 
2 3 
40 40 100 
100 2 
20 5 
1 1000 
1 1 
999
 
1 
2 
-1

题意: 给出Boss的血量以及你可以使用的n个技能,每个技能最多用一次,并且每个技能使用完后有一段真空期ti不能释放其他技能,每个技能可以造成持续伤害,一共持续len秒,每秒造成di伤害,问最早击败Boss的时间,时间从0开始计算。

分析: 观察题目发现技能个数很小,所以猜测可以状压dp来枚举技能释放顺序,另外为了求最早击败时间,所以可以二分时间,然后check一下在这个时间内能否击败Boss,check函数里面就是一个状压dp求最大伤害的过程,这个过程和常规状压dp类似,就是枚举上一次释放的技能,只是在计算伤害的时候需要注意留给当前技能的剩余时间,设x表示二分的时间,t表示留给当前技能的时间,为了简化判断的过程,可以预处理出来一个数组time[i],记录i这个状态需要的真空期加和,那么t就等于x-time[上一个状态],t的取值有三种情况,第一种为负数,这说明现在还处于上一个技能的真空期中,那显然此时能造成的伤害就是上一个状态的伤害,第二种情况t∈[0, len],len是当前技能持续时间,这说明该技能伤害只打出来了一部分,此时造成伤害就是上一个状态伤害加该技能伤害的sum[t],第三种情况t > len,这说明该技能伤害全部打满,此时伤害就是上一个状态伤害加该技能sum[len],这三种情况考虑不全很容易出错!

每次更新完一个状态的伤害dp[i]后都可以和hp比较一下,如果大于等于hp那就说明x时间内可以击败Boss,如果没有状态能击败,那就说明x时间内不能击败Boss,最后输出二分结果即可。

具体代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#define int long long
using namespace std;

int n, hp, dp[1<<20], time[1<<20];
struct node{
	int t, len;
	int sum[100005];
}ski[20]; 

int st;

bool check(int x){//在x时间内是否可以ko 
	for(int i = 0; i < 1<<n; i++){
		dp[i] = 0;
		for(int j = 1; j <= n; j++){
			if(i&(1<<(j-1))){
				int t = x-time[i^(1<<(j-1))];
				if(t < 0) dp[i] = max(dp[i], dp[i^(1<<(j-1))]);
				else{
					if(t > ski[j].len) t = ski[j].len;
					dp[i] = max(dp[i], dp[i^(1<<(j-1))]+ski[j].sum[t]);
				}
			}
		}
		st = i;
		if(dp[i] >= hp) return true;
	}
	return false;
}

signed main()
{
	int T;
	cin >> T;
	while(T--){
		scanf("%lld%lld", &n, &hp);
		int l = 0, r = 0, ans = -1; 
		for(int i = 1; i <= n; i++){
			scanf("%lld%lld", &ski[i].t, &ski[i].len);
			r += max(ski[i].t, ski[i].len);
			for(int j = 1; j <= ski[i].len; j++){
				int t;
				scanf("%lld", &t);
				ski[i].sum[j] = ski[i].sum[j-1]+t;
			}
		}
		for(int i = 0; i < 1<<n; i++){
			time[i] = 0;
			for(int j = 1; j <= n; j++){
				if(i&(1<<(j-1)))
					time[i] += ski[j].t;
			}
		}
		while(l <= r){
			int mid = l+r>>1;
			if(check(mid)){
				r = mid-1;
				ans = mid;
			}
			else l = mid+1;
		}
		if(ans != -1) printf("%lld\n", ans-1);
		else puts("-1");
	}
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值