Loading

P10220 [省选联考 2024] 迷宫守卫 题解

说一下自己赛时做法。赛时会了,但没能调出来,几乎确定进不去队了,留下这篇题解作为这次比赛的记录吧。

确定做法是对的,有同学用该做法过了所有大样例。

称激活守卫为打开开关。

首先考虑,如果确定所有开关的情况,Bob 有一个简单的贪心做法:当走到一个点时,递归其左右子树并得到两个序列,若右子树的对应序列的小于左子树的对应序列,则右边更优,若开关未被打开,他就会往右走;否则往左走。

考虑能否记录一些简单的东西供 A 参考。

首先,记录剩余魔力值一定是不可能的,这启发我们记录某个决策需要花费的最小魔力值,若剩余魔力值大于它,则该决策可行。

那么我们需要比较两棵子树构成的序列,并据此给出代价。

注意到叶节点上是排列,因此只需记录序列的第一位即可比较大小。因此有自然的想法:在节点 \(u\) 上记录很多 \((v, d)\),表示:站在 \(u\) 上遍历其子树,若 \(< q_v\) 的所有节点都不可达,且 \(q_v\) 可达,需要花费的最小代价。

求出 \((v, d)\) 后,每次贪心选择让 \(v\) 最大的方案并往下走。假设 \(v\) 在左子树,那么,先把魔力值全部供左子树使用,再将多余的魔力值给右子树使用。为了保证左右子树独立,我们可以再添加一个数 \(t\),表示,为了让 \(u\)\(< q_v\) 的节点不可达,异于 \(v\) 一侧需要花费的最小代价。容易发现扣除 \(t\) 后向下递归,将多余的魔力值与 \(t\) 一起传进另一侧的子树,最优,且一定合法。

显然,魔力值越大,就可以让一棵子树的字典序越大,这里允许不用完的情况。

最优性:首先此时 \(v\) 最大。因为异于 \(v\) 一侧花费的魔力值最小,\(v\) 所在子树能获得最多的魔力值,即字典序一定最大。

合法性:另一侧的子树可达的权值最小点一定大于 \(q_v\),字典序增大后仍然成立。

所有 \((v, d, t)\) 加起来不超过 \(n 2^n\) 个,如果我们可以在获得 \(u\) 的左右儿子的 \((v, d, t)\) 的情况下,\(O(siz)\) 求出 \(u\) 的所有 \((v, d, t)\),则问题被解决。

考虑按 \(v\) 做归并排序。

假设当前左侧扫到 \((v_l, d_l, t_l)\),右侧扫到 \((v_r, d_r, t_r)\)。设左侧小于右侧,则 \(d=\min\{w_u, d_r\}\);否则 \(d=d_l\),当 \(l, r\) 不存在时视为正无穷。\(t\) 可以简单讨论得出,需要同时记录是否开启 \(w_u\)。我实现时 \(t=-1\) 代表开启 \(w_u\),因为显然此时不需要为右边预留魔力值。

有一个细节是,如果剩余的魔力值足够右侧子树的最小值大于当前 \(v_l\),则可以不开启 \(w_u\),尽管最小化总花费时需要开启 \(w_u\)

综上,这道题做完了。

#include <bits/stdc++.h>

using LL = long long;

const LL inf = __LONG_LONG_MAX__ / 2;

struct tp {
  LL d; int v; LL p;
  tp(LL d, int v, LL p) : d(d), v(v), p(p) {}
  bool operator < (const tp &t) const {
    return d == t.d ? v < t.v : d < t.d;
  }
} ;

void solve() {
  int n; LL K; scanf("%d %lld", &n, &K);
  int siz = (1 << (n + 1)) + 1;
  std::vector<LL> w(1 << n);
  std::vector<int> v(siz);
  // std::vector<LL> prz(1 << (n + 1)); 
  std::vector<std::vector<int>> son(siz);
  for (int i = 1; i < (1 << n); i++) scanf("%lld", &w[i]);
  for (int i = (1 << n); i < (1 << (n + 1)); i++) scanf("%d", &v[i]);
  std::vector<std::vector<tp>> a(siz);
  std::function<void(int)> dfs1 = [&](int u) {
    if (u >= (1 << n)) {
      son[u].push_back(v[u]);
      a[u].emplace_back(0, v[u], 0);
      return ;
    }
    int ls = u << 1, rs = u << 1 | 1;
    dfs1(ls), dfs1(rs);
    for (auto x : son[ls]) son[u].push_back(x);
    for (auto x : son[rs]) son[u].push_back(x);
    std::sort(son[u].begin(), son[u].end());
    int lsiz = a[ls].size(), rsiz = a[rs].size();
    int l = 0, r = 0;
    LL ml = 0, mr = 0;
    while (l != lsiz || r != rsiz) {
      LL p = 0;
      bool f = 1;
      if (r == rsiz || (l != lsiz && a[ls][l].v < a[rs][r].v)) {
        if ((r < rsiz ? a[rs][r].d : inf) > w[u]) {
          p = -1;
        } else {
          p = a[rs][r].d;
        }
        LL d = a[ls][l].d + std::min((r < rsiz ? a[rs][r].d : inf), w[u]);
        a[u].emplace_back(d, a[ls][l].v, p);
        if (p == -1) assert(d >= w[u]);
        ++l;
      } else {
        if (l < lsiz) {
          p = (l < lsiz ? a[ls][l].d : inf);
          a[u].emplace_back(p + a[rs][r].d, a[rs][r].v, p);
        }
        ++r;
      }
    }
    std::vector<tp> b;
    for (int i = a[u].size() - 1; i >= 0; i--) {
      if (!b.size() || a[u][i].d <= b.back().d)
        b.push_back(a[u][i]);
    }
    std::reverse(b.begin(), b.end());
    a[u] = b;
    for (int i = 0; i < a[u].size() - 1; i++)
      assert(a[u][i].d <= a[u][i + 1].d);
    // printf("\n");
    // printf("%d : \n", u);
    // for (const auto &[d, v, p] : a[u])
    //   printf("%lld %d %lld\n", d, v, p);
    // printf("\n");
  } ;
  dfs1(1);
  std::vector<int> path;
  auto find = [&](int u, LL k) {
    return (--std::upper_bound(a[u].begin(), a[u].end(), tp(k, int(1e9), int(1e9))));
  } ;
  std::function<LL(int, LL)> dfs2 = [&](int u, LL k) {
    // printf("dfs %d %lld\n", u, k);
    if (u >= (1 << n)) {
      path.push_back(v[u]);
      return k;
    }
    auto it = find(u, k);
    int v = it->v;
    LL p = it->p;
    // printf("%d -> %d %lld %lld %lld\n", u, v, it->d, k, it->p);
    int ls = u << 1, rs = u << 1 | 1;
    auto iter = std::lower_bound(son[ls].begin(), son[ls].end(), v);
    if (iter != son[ls].end() && *iter == v) {
      // printf("l\n");
      if (p == -1) {
        LL res = dfs2(ls, k - w[u]);
        if (find(rs, res + w[u])->v >= v) res += w[u];
        return dfs2(rs, res);
      } else {
        LL res = dfs2(ls, k - p);
        return dfs2(rs, res + p);
      }
    } else {
      // printf("r\n");
      LL res = dfs2(rs, k - p);
      return dfs2(ls, res + p);
    }
  } ;
  dfs2(1, K);
  for (int i = 0; i < (1 << n); i++) printf("%d ", path[i]);
  printf("\n");
}

int main() {
  int T; scanf("%d", &T); while (T--) {
    solve();
    // printf("=-------\n");
  }
}
posted @ 2024-03-04 13:29  purplevine  阅读(88)  评论(1编辑  收藏  举报