【HNOI 2017】大佬

Problem

Description

人们总是难免会碰到大佬。他们趾高气昂地谈论凡人不能理解的算法和数据结构,走到任何一个地方,大佬的气场就能让周围的人吓得瑟瑟发抖,不敢言语。你作为一个 OIer,面对这样的事情非常不开心,于是发表了对大佬不敬的言论。 大佬便对你开始了报复,你也不示弱,扬言要打倒大佬。现在给你讲解一下什么是大佬,大佬除了是神犇以外,还有着强大的自信心,自信程度可以被量化为一个正整数 (C),想要打倒一个大佬的唯一方法是摧毁 Ta 的自信心,也就是让大佬的自信值等于 (0)(恰好等于 (0),不能小于 (0))。由于你被大佬盯上了,所以你需要准备好 (n) 天来和大佬较量,因为这 (n) 天大佬只会嘲讽你动摇你的自信,到了第 (n+1) 天,如果大佬发现你还不服,就会直接虐到你服,这样你就丧失斗争的能力了。

你的自信程度同样也可以被量化,我们用 (mathrm{mc}) 来表示你的自信值上限。在第 (i (ige 1)) 天,大佬会对你发动一次嘲讽,使你的自信值减小 (a_i),如果这个时刻你的自信值小于 (0) 了,那么你就丧失斗争能力,也就失败了(特别注意你的自信值为 (0) 的时候还可以继续和大佬斗争)。 在这一天,大佬对你发动嘲讽之后,如果你的自信值仍大于等于 (0),你能且仅能选择如下的行为之一

  1. 还一句嘴,大佬会有点惊讶,导致大佬的自信值 (C) 减小 (1)

  2. 做一天的水题,使得自己的当前自信值增加 (w_i),并将新自信值和自信值上限 (mathrm{mc}) 比较,若新自信值大于 (mathrm{mc}),则新自信值更新为 (mathrm{mc})。例如,(mathrm{mc} = 50),当前自信值为 (40),若 (w_i = 5),则新自信值为 (45),若 (w_i = 11),则新自信值为 (50)

  3. 让自己的等级值 (L)(1)

  4. 让自己的讽刺能力 (F) 乘以自己当前等级 (L),使讽刺能力 (F) 更新为 (Fcdot L)

  5. 怼大佬,让大佬的自信值 (C) 减小 (F)。并在怼完大佬之后,你自己的等级 (L) 自动降为 (0),讽刺能力 (F) 降为 (1)。由于怼大佬比较掉人品,所以这个操作只能做不超过两次

特别注意的是,在任何时候,你不能让大佬的自信值为负,因为自信值为负,对大佬来说意味着屈辱,而大佬但凡遇到屈辱就会进化为更厉害的大佬直接虐飞你。在第 (1) 天,在你被攻击之前,你的自信是满的(初始自信值等于自信值上限 (mathrm{mc})),你的讽刺能力 (F)(1),等级是 (0)

现在由于你得罪了大佬,你需要准备和大佬正面杠,你知道世界上一共有 (m) 个大佬,他们的嘲讽时间都是 (n) 天,而且第 (i) 天的嘲讽值都是 (a_i)。不管和哪个大佬较量,你在第 (i) 天做水题的自信回涨都是 (w_i)。这 (m) 个大佬中只会有一个来和你较量((n) 天里都是这个大佬和你较量),但是作为你,你需要知道对于任意一个大佬,你是否能摧毁他的自信,也就是让他的自信值恰好等于 (0)。和某一个大佬较量时,其他大佬不会插手。

Input Format

第一行三个正整数 (n,m,mathrm{mc})。分别表示有 (n) 天和 (m) 个大佬,你的自信上限为 (mathrm{mc})

接下来一行是用空格隔开的 (n) 个数,其中第 (i(1le ile n)) 个表示 (a_i)

接下来一行是用空格隔开的 (n) 个数,其中第 (i(1le ile n)) 个表示 (w_i)

接下来 (m) 行,每行一个正整数,其中第 (k(1le kle m)) 行的正整数 (C_k) 表示第 (k) 个大佬的初始自信值。

Output Format

(m) 行,如果能战胜第 (k) 个大佬(让他的自信值恰好等于 0),那么第 (k) 行输出 (1),否则输出 (0)

Sample

Input

30 20 30
15 5 24 14 13 4 14 21 3 16 7 4 7 8 13 19 16 5 6 13 21 12 7 9 4 15 20 4 13 12
22 21 15 16 17 1 21 19 11 8 3 28 7 10 19 3 27 17 28 3 26 4 22 28 15 5 26 9 5 26
30
10
18
29
18
29
3
12
28
11
28
6
1
6
27
27
18
11
26
1

Output

0
1
1
0
1
0
1
1
0
0
0
1
1
1
1
1
1
0
0
1

Range

对于 (20\%) 的数据,(1le nle 10)

另有 (20\%) 数据,(1le C_i,n,mathrm{mc}le 30)

对于 (100\%) 的数据,(1le n, mathrm{mc}le 100, 1le mle 20; 1le a_i, w_ilemathrm{mc}, 1le C_ile 10^8)

Algorithm

(DP),广搜

Mentality

我感觉我做了一道搜索题 (......) 蓝瘦。而且题面也太过真实了。

我们第一个瞬间就该发现,每个大佬只有血量不同,那么我们为了打败大佬,肯定要用尽量多的时间来降低他的自信,则由于其他量都相等,我们完全可以先求出最多能用多少天来与大佬战♂斗对抗。

这个很简单,来一发 (DP) 就好,设 (f[i][j]) 为到了第 (i) 天剩余 (j) 点自信时,最多能花多少天嘲讽大佬。然后取 (n^2) 数组内的最大值 (D) 作为最大天数。

然后我们要统计如何分配怼大佬的那两次。

首先,如果大佬的自信值小于等于 (D) ,我们直接不停还嘴就行。

那么我们讨论一下怼大佬的情况。

先计算出我们的嘲讽能力为 (F) 时所需花费的最小天数 (D) ,求法后面再讲,它太过暴力了 (emm...)

我们考虑枚举两次怼大佬时的嘲讽能力 (F1,F2) ,显然在相同 (F) 值的情况下花在准备上的时间要越少越好,我们设两者的时间为 (D1,D2)

那么我们这两次怼大佬必定要满足两个条件:不能把大佬怼死了;剩下的时间通过还嘴可以刚好打败大佬。

转化成不等式如下:

[F1+F2le C ,F1+F2+(D-D1-D2)ge C ]

那我们只需要枚举一个 (F1) ,在这种情况下,(F1,D1) 都已经固定了,我们可以找到一个满足第一个不等式的,具有最优性的 (F2) ,也即 (F2-D2) 在满足第一个不等式的条件下最小,那么这时我们计算 (F1+F2+(D-D1-D2)) 是否大于等于 (C) ,如果大于则此次询问答案为 (YES) ,如果枚举遍所有的 (F1) 都无法满足第二个不等式那答案就为 (NO)

所以我们可以把所有计算出来的状态按 (F) 排序,然后从大到小枚举 (F1) ,而 (F2) 的寻找范围也是单调递增的,那么我们扫 (F2) 的指针沿用上次的即可。

那么重点来了,怎么计算状态呢?答案是 -- 广搜 (......)

我们只需要带着三个数值所代表的状态 (dfs) 即可,分别是 (step) -- 使用 (step) 天,(L) -- 当前等级,(F) -- 当前嘲讽能力。然后直接广搜肯定是不行的,我们来观察一下:由于在相同的 (F) 下,所花天数越小越好。而由于我们使用的是广搜,所以当我们搜索到一个新状态 (step,L,F) 时,相对于当前的 (L,F)(step) 就肯定是最优天数了。所以我们只需要使用哈希表判重,如果后面再搜到相同的 (L,F) ,那就不加入搜索队列了。

暴力否?

如若不懂详见代码。

为了卡常长得蛮奇怪。

Code

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
#define f(i) STA[i].first
#define d(i) STA[i].second
const int mod = 9e5 + 1;
int n, m, limit, maxx, cnt, C, a[101], w[101], c[21];
int f[101][101], D;
inline void read(int &x) {
  x = 0;
  char ch = getchar();
  while (!isdigit(ch)) ch = getchar();
  while (isdigit(ch)) {
    x = x * 10 + ch - '0';
    ch = getchar();
  }
}
pair<int, int> STA[1000001];
struct node {
  int L, F, t;
};
struct Check {
  int x[mod], y[mod], nx[mod], head[mod], cnt;
  int get_key(int X, int Y) { return (998244ll * X + Y) % mod; }
  inline void add(int X, int Y) {
    int now = get_key(X, Y);
    cnt++;
    nx[cnt] = head[now], x[cnt] = X, y[cnt] = Y;
    head[now] = cnt;
  }
  inline bool query(int X, int Y) {
    int now = get_key(X, Y);
    for (register int i = head[now]; i; i = nx[i])
      if (X == x[i] && Y == y[i]) return true;
    return false;
  }
} Map, M;
inline void Max(int &a, int b) { a = a < b ? b : a; }
queue<node> q;
inline void bfs() {
  q.push((node){0, 1, 1});
  while (!q.empty()) {
    node now = q.front();
    q.pop();
    if (now.t == D) continue;
    q.push((node){now.L + 1, now.F, now.t + 1});
    if (now.L > 1 && 1ll * now.L * now.F <= maxx &&
        !Map.query(now.L * now.F, now.L))  //手写 Map 判重
    {
      int A = now.L * now.F, B = now.t + 1;
      q.push((node){now.L, A, B});
      if (!M.query(A, 9181283))
        STA[++cnt] = make_pair(A, B),
        M.add(A, 9181283);  //相同 F 下所花天数越少越好
      Map.add(A, now.L);
    }
  }
}
int main() {
  read(n), read(m), read(limit);
  for (register int i = 1; i <= n; i++) read(a[i]);
  for (register int i = 1; i <= n; i++) read(w[i]);
  for (register int i = 1; i <= m; i++)
    read(c[i]), Max(maxx, c[i]);  //先处理出搜索的上限 -- F
                                  //值至少不能大于自信值最强的大佬的自信吧
  for (register int i = 1; i <= n; i++)
    for (register int j = a[i]; j <= limit; j++)
      Max(f[i][j - a[i]], f[i - 1][j] + 1),
          Max(f[i][min(limit, j - a[i] + w[i])],
              f[i - 1][j]);  //先 DP 出最大天数
  for (register int i = 1; i <= n; i++)
    for (register int j = 1; j <= limit; j++) Max(D, f[i][j]);  //取 max
  bfs();                                                        //开始广搜
  sort(STA + 1, STA + cnt + 1);
  for (register int i = 1; i <= m; i++) {
    if (c[i] <= D) {
      printf("1
");
      continue;
    }
    int mmax = -1e9, Ans = 0;
    for (register int l = 1, r = cnt; r; r--) {
      while (f(l) + f(r) <= c[i] && l < cnt)
        Max(mmax, f(l) - d(l)), l++;                //移动
      if (f(r) + D - d(r) + mmax >= c[i]) Ans = 1;  //是否满足第二个不等式
      if (f(r) <= c[i] && f(r) + D - d(r) >= c[i]) Ans = 1;
    }
    printf("%d
", Ans);
  }
}

原文地址:https://www.cnblogs.com/luoshuitianyi/p/10679302.html