新千题计划 8#:[AH/HNOI 2017] 大佬

大佬 大佬在 n n n 天内第 i i i 天对你造成 a i a_i ai 伤害。你的初始血量 m c mc mc,等级 0,讽刺能力 1,每天可以选择以下操作之一:对大佬造成 1 伤害;回 w i w_i wi 血但不能超过 m c mc mc;使等级加 1;使讽刺能力乘等级;怼大佬,即对大佬造成等于讽刺能力的伤害,并使等级和讽刺能力恢复至初始——最后一种操作至多两次。若在 n n n 天内你血量非正数则你死亡。若在 n n n 天内大佬血量恰为 0 则大佬死亡,大佬的血量不能为负数。给出 m m m 个大佬初始血量,分别输出能否战胜之。

贪心。 本题难在实现,0xis 做到一半,本题在洛谷上从紫题变成黑题。普及考也说得过去,不过确实好题。

由于自己血量不影响伤害,可以先 DP 算出能造成伤害的最大天数 d r dr dr,并带哈希 BFS 得到所有「蓄力天数 d a y day day—伤害 f f f」数对 。如果不怼大佬可以战胜则 h p ≤ d r hp \le dr hpdr。怼一次则 f ≤ h p f \le hp fhp h p − f ≤ d r − d a y hp - f \le dr - day hpfdrday,怼两次则 f 1 + f 2 ≤ h p f_1 + f_2 \le hp f1+f2hp h p − f 1 − f 2 ≤ d r − d a y 1 − d a y 2 hp - f_1 - f_2 \le dr - day_1 - day_2 hpf1f2drday1day2

将数对排序可使决策单调。不怼可直接判断,怼一次可枚举。对于怼两次,枚举第二次怼,在 f 1 + f 2 ≤ h p f_1 + f_2 \le hp f1+f2hp 情况下扫第一次怼,使 d a y 1 − f 1 day_1 - f_1 day1f1 最小判断即可。

#include <cstdio>
#include <algorithm>
#include <queue>
#define F(z, u, v) for(int z = (u), dest##z = (v); z <= dest##z; z++)
const int MAXN = 101, MAXT = 10000001, YIYI = 100000000;
typedef long long int LINT;
int n, m, mc, c, a[MAXN], w[MAXN], dp[MAXN][MAXN], dr, tc = 0;
#define Gt(u, v) ((u) = std::max((u), (v)))
#define Lt(u, v) ((u) = std::min((u), (v)))

struct TUP { int day, f, l; TUP() {}
  TUP(int day, int f, int l): day(day), f(f), l(l) {}} tup[MAXT];
std::queue<TUP> q;

namespace Hash {
  const int MAXH = 100000, MAXS = 10000001;
  int cnt = 0, hd[1 + MAXH], f[MAXS], l[MAXS], dd[MAXS], po[MAXS];
  int Code(int day, int u, int v) {
    return 1 + ((LINT(u ^ 19260817) * day) ^ 20180801) % MAXH; }
  void Add(int day, int u, int v) {
    int k = Code(day, u, v); po[++cnt] = hd[k]; hd[k] = cnt;
    f[cnt] = u; dd[cnt] = day; }
  bool At (int day, int u, int v) {
    for(int i = hd[Code(day, u, v)]; i; i = po[i])
      if(day == dd[i] && u == f[i]) return true;
    return false; }}

int main() {
  scanf("%d%d%d", &n, &m, &mc);
  F(i, 1, n) scanf("%d", a + i); F(i, 1, n) scanf("%d", w + i);
  F(i, 1, n) F(j, a[i], mc)
    Gt(dr, std::max(Gt(dp[i][j - a[i]], dp[i - 1][j] + 1),
     Gt(dp[i][std::min(j + w[i] - a[i], mc)], dp[i - 1][j])));

  q.push(TUP(1, 1, 0)); while(!q.empty()) {
    int day = q.front().day, f = q.front().f, l = q.front().l; q.pop();
    if(day >= dr) continue; q.push(TUP(day + 1, f, l + 1));
    if(l > 1 && LINT(f) * l <= YIYI && !Hash::At(day + 1, f * l, l))
      Hash::Add(day + 1, f * l, l),
      q.push(tup[++tc] = TUP(day + 1, f * l, l)); }

  std::sort(tup + 1, tup + tc + 1, [](TUP u, TUP v) {
   return u.f != v.f? u.f < v.f: u.day < v.day; });

  while(m--) {
    int hp, lag = 0x70000000; bool f_ = true; scanf("%d", &hp);
    if(hp <= dr) { puts("1"); continue; }
    for(int l = 1, r = tc; r; r--) {
      while(l < tc && tup[l].f + tup[r].f <= hp)
        Lt(lag, tup[l].day - tup[l].f), l++;
      if((lag + tup[r].day - tup[r].f + hp <= dr) ||
       (tup[r].f <= hp && hp - tup[r].f + tup[r].day <= dr)) {
        f_ = false; puts("1"); break; }}
    if(f_) puts("0"); }}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值