练习本

Codeforces Round 1044 (Div. 2) ABCD

题目🔗 Codeforces Round 1044 (Div. 2)

A. Redstone?

列公式发现要求最后一个齿轮的大小要和第一个齿轮的大小一样。也就是要求数组中存在出现至少两次的数字。

void solve() {
  int n;
  cin >> n;

  vector<int> a(n + 1), vis(120);
  bool ok = false;
  For1(i, 1, n) {
    cin >> a[i];
    if (vis[a[i]]) {
      ok = true;
    }
    vis[a[i]] = true;
  }
  if (ok)
    YES();
  else
    NO();
}

B. Villagers

为了让总花费最小,把数组排序,选择最大的两个,让这两个元素和解。这样从大到小两两配对即可。

void solve() {
  int n;
  cin >> n;

  vector<int> a(n + 1);
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  sort(a.begin() + 1, a.begin() + 1 + n);
  ll ans{};
  for (int i = n; i >= 1; i -= 2) {
    ans += a[i];
  }
  cout << ans << '\n';
}

C. The Nether

以每个节点作为起点询问,容易找到最大路径的长度 $len$

考虑如何找到路径的第二个节点,设这个节点是 $p$ ,那么以 $p$ 开头的最长路径必然是 $len - 1$ , 并且以 $start$ 作为起点, $p$ 作为终点,路径长度需要恰好是 2.

在第一遍询问的过程中,我们可以记录每个长度对应的点的列表。我们就可以容易找到 $len - 1$ 对应的点,然后以 $start$ 为起点, $p$ 作为另外一个点询问,找答案恰好是 2 的元素。依此类推,可以找到所有的 $len$ 个点。

问题的关键:我们发现第二遍询问的过程中,每个点最多只会被询问一次。所以这个做法是正确的。

void solve() {
  int n;
  cin >> n;

  auto query = [&](int start) -> int {
    cout << "? " << start << " " << n;
    for (int i = 1; i <= n; i++) {
      cout << " " << i;
    }
    cout << endl;

    int len;
    cin >> len;
    return len;
  };
  auto query2 = [&](int p1, int p2) -> bool {
    cout << "? " << p1 << " 2 " << p1 << ' ' << p2 << endl;
    int len;
    cin >> len;
    return len == 2;
  };

  map<int, vector<int>> mp;
  int mx = 0;
  for (int i = 1; i <= n; i++) {
    int len = query(i);
    mp[len].push_back(i);
    ckmax(mx, len);
  }

  vector<int> ans;
  int p1 = mp[mx][0], len = mx;
  ans.push_back(p1);
  for (int i = len - 1; i >= 1; i--) {
    for (auto v : mp[i]) {
      if (query2(p1, v)) {
        ans.push_back(v);
        p1 = v;
        break;
      }
    }
  }

  cout << "! " << mx;
  for (auto i : ans) {
    cout << " " << i;
  }
  cout << endl;
}

D. Chicken Jockey

有趣的 DP 题

注意到我们需要造成的伤害总量是固定的,总伤害分为两个部分:坠落伤害和普攻伤害。我们需要最大化坠落伤害,最小化普攻伤害。

设 $d_i$ 代表,消灭前 $i$ 个元素,需要的普攻伤害的最小值。

对于位置 $i$ ,如果要对它造成坠落伤害,我们需要考虑前 $i - 1$ 个位置。直接对 $i$ 造成坠落伤害的时刻,一定是 $i - 1$ 消失的时刻。

最简单的情况是,直接杀掉 $i - 1$ ,此时状态转移是 $d_{i - 2} + a_{i - 1} + max(0, a_i - (i - 1))$

另外一种情况是,不直接杀掉 $i - 1$ , 总之我们需要让前 $i - 1$ 给被消灭,此时只剩下第 $i - 1$ 个元素,第 $i$ 个元素的坠落伤害只有 1. 而消灭前 $i - 1$ 个元素的最小代价,恰好是我们的状态定义,这是一个子问题,因此状态转移是 $d_i = d_{i - 1} + (a_i - 1)$

void solve() {
  int n;
  cin >> n;

  VI a(n + 1);
  for (int i = 1; i <= n; i++)
    cin >> a[i];

  VL d(n + 1, INFL);
  d[0] = 0;
  d[1] = a[1];

  For1(i, 2, n) {
    d[i] = d[i - 1] + a[i] - 1;
    ckmin(d[i], d[i - 2] + a[i - 1] + max(0, a[i] - (i - 1)));
  }

  cout << d[n] << '\n';
}