题目链接:https://codeforces.com/gym/102028
B. Ultraman vs. Aodzilla and Bodzilla
题意:
两只怪兽,它们的生命和攻击分别为hpA,hpB,attA,attB,现在你要打败它们,第i回合你的攻击为i。问在承受伤害最少的前提下,攻击序列字典序最小是怎样的。攻击A就是A,攻击B就是B。最后输出承受的最小伤害和攻击序列。
题解:
最后的答案肯定是总回合最小时产生的,但是先打死谁不一定,答案就是两种情况的最小值。之后考虑贪心地构造攻击序列。
这里用了种很巧妙地方法吧,定义溢出伤害为最后一击多余出来的伤害。
我们先考虑先将A打死,然后打B的情况。这种字典序已经尽可能小的,只要保证回合数最小就行了。怎么判断回合数最小呢,设Ra为击败A时的溢出伤害,Rtot为最后一击时的溢出伤害(包含了A溢出的伤害),这里Rtot的计算方法参考代码,如果现在Ra > Rtot,说明如果不要A的溢出伤害,可能会多打一回合,那么这时就应该在某一回合打B,这里的位置我们选择尽可能后面的就是Ra(细节考虑一下)。
另外一种情况,我们就先尽可能地将前面的换成A,最后再来判断Rb和Rtot的关系,如果有Rb > Rtot(注意这里Rb会不断减小,因为我们换了一些操作),那么此时如果没有Rb就要多打一回合,设最后一次操作在第i回合,显然现在Rb < i + 1,直接攻击是攻击不了的。我们此时将最后一次操作撤销,将第i + Rb - Rtot次攻击置为A即可,这样也满足最优(A尽可能靠前并且回合数最小)。
细节可以琢磨一下。
代码如下:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 2e5 + 5; int T; ll ha, hb, atta, attb; ll Get_sum(ll x) { return 1ll * x * (x + 1) / 2; } ll Get_round(ll x) { ll ans = 1; while(Get_sum(ans) < x) ans++; return ans ; } int main() { ios::sync_with_stdio(false) ;cin.tie(0) ; cin >> T; while(T--) { cin >> ha >> hb >> atta >> attb; ll ra = Get_round(ha), rb = Get_round(hb), rtot = Get_round(ha + hb); string res(rtot,'B') ; ll ans = min(atta * ra + attb * rtot , attb * rb + atta * rtot) ; if(atta * ra + attb * rtot == ans) { string cur(rtot,'A') ; for(int i = ra ; i < rtot ; i++) cur[i] = 'B' ; ll remain_a = Get_sum(ra) - ha , remain_tot = Get_sum(rtot) - ha - hb ; if(remain_a > remain_tot) { cur[remain_a - 1] = 'B' ; } res = min(res ,cur) ; } int last; if(attb * rb + atta * rtot == ans) { string cur(rtot,'B') ; for(int i = rb ; i < rtot; i++) cur[i] = 'A' ; ll remain_b = Get_sum(rb) - hb; ll remain = remain_b - Get_sum(rtot) + ha + hb; for(int i = 0; i < ra ; i++) { if(remain_b >= i + 1) { cur[i] = 'A' ; remain_b -= i + 1; remain -= i + 1; last = i ; } } if(remain > 0) { cur[last] = 'B'; cur[last + remain] = 'A' ; } res = min(res, cur) ; } cout << ans << ' ' << res << '\n' ; } return 0 ; }