Edu162

Edu162[A-E]

A. Moving Chips[Trick]

功能区分为 \(n\) 个单元格,从左到右编号为 \(1\)\(n\) 。每个单元要么包含一个芯片,要么是免费的。

您可以执行以下操作任意次数(可能为零):选择一个筹码并将其移动到 左侧最近的空闲单元。您可以选择任何您想要的芯片,只要其左侧至少有一个空闲单元即可。当您移动芯片时,操作前所在的单元格就会空闲。

您的目标是以这样的方式移动芯片,使得它们形成一个块,并且它们之间没有任何空闲单元。您必须执行的最少操作次数是多少?


分析

我们找出最左边和最右边的芯片\(L,R\)

目标使得\([L,R]\) 之中没有空白格

我们每次移动最右边的芯片,可以填补最右边的空白,并将空出的空白格移到\(R\) 右边

这样我们每次移动都可以减少一个空白格的数量

我们的目标是让空白格的数量为\(0\)

所需的操作次数就是一开始区间中空白格的数量

code

Submission #247902579 - Codeforces

// Codeforces - Educational Codeforces Round 162 (Rated for Div. 2) A. Moving
// Chips https://codeforces.com/contest/1923/problem/0 2024-02-23 22:35:51

#include <bits/stdc++.h>
using namespace std;
#define all(a) begin(a), end(a)
#define int long long
int n, m;
void solve() {
  cin >> n;
  vector<int> a(n), s(n);
  for (auto &i : a) cin >> i;
  for (int i = 0; i < n; i++)
    if (i)
      s[i] = s[i - 1] + a[i];
    else
      s[i] = a[i];
  int l = 0, r = n - 1;
  while (l < n and a[l] == 0) l++;
  while (r >= 0 and a[r] == 0) r--;
  if (r < l)
    cout << "0\n";
  else if (l)
    cout << -s[r] + s[l - 1] + r - l + 1 << "\n";
  else
    cout << -s[r] + r - l + 1 << "\n";
}

signed main() {
  ios::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  int __;
  cin >> __;
  while (__--) solve();
  return 0;
}

B. Monsters Attack![Trick]

您正在玩电脑游戏。该游戏的当前等级可以建模为一条直线。您的角色位于该行的点 \(0\) 。有 \(n\) 个怪物试图杀死你的角色;第 \(i\) 个怪物的生命值等于 \(a_i\) ,并且最初位于点 \(x_i\)

每一秒都会发生以下情况:

  • 首先,你向怪物发射最多 \(k\) 颗子弹。每颗子弹只瞄准一个怪物并使其生命值降低 \(1\) 。对于每颗子弹,您可以任意选择其目标(例如,您可以向一个怪物发射所有子弹,向不同的怪物发射所有子弹,或选择任何其他组合)。任何怪物都可以成为子弹的目标,无论其位置和任何其他因素如何;
  • 然后,所有生命值小于等于 \(0\) 的活着的怪物都会死亡;
  • 然后,所有活着的怪物都会向您靠近 \(1\) 点(您左边的怪物将其坐标增加 \(1\) ,您右侧的怪物将其坐标减少 \(1\) )。如果任何怪物到达你的角色(移动到点 \(0\) ),你就输了。

您能生存并杀死所有 \(n\) 个怪物而不让它们接触到您的角色吗?

分析

这个题也是一个小Trick

我们维护前缀 \(X\) 秒能不能 消灭所有 前 \(X\) 秒会靠近主角的怪兽就好

如果坐标为负数我们就取绝对值 转移到正数 方便处理

code

Submission #247906784 - Codeforces

// Codeforces - Educational Codeforces Round 162 (Rated for Div. 2) B. Monsters
// Attack! https://codeforces.com/contest/1923/problem/B 2024-02-23 22:47:36

#include <bits/stdc++.h>
using namespace std;
#define all(a) begin(a), end(a)
#define int long long
int n, k;
void solve() {
  cin >> n >> k;
  vector<int> a(n), x(n);
  for (auto &i : a) cin >> i;
  for (auto &i : x) cin >> i;
  map<int, int> mp;
  for (int i = 0; i < n; i++)
    if (x[i] < 0)
      mp[-x[i]] += a[i];
    else
      mp[x[i]] += a[i];
  int res = 0;
  for (auto [v, w] : mp)
    if (res + w <= v * k)
      res += w;
    else
      return (void)(cout << "NO\n");
  cout << "YES\n";
}

signed main() {
  ios::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  int __;
  cin >> __;
  while (__--) solve();
  return 0;
}

C. Find B [贪心]

如果存在长度为 \(m\) 的数组 \(b\) ,并且满足以下条件,则长度为 \(m\) 的数组 \(a\) 被认为是良好的:

  1. \(\sum\limits_{i=1}^{m} a_i = \sum\limits_{i=1}^{m} b_i\)
  2. \(a_i \neq b_i\) 对于从 \(1\)\(m\) 的每个索引 \(i\)
  3. \(b_i>0\) 对于从 \(1\)\(m\) 的每个索引 \(i\)

给您一个长度为 \(n\) 的数组 \(c\) 。该数组的每个元素都大于 \(0\)

您必须回答 \(q\) 个查询。在第 \(i\) 个查询期间,您必须确定子数组 \(c_{l_{i}}, c_{l_{i}+1}, \dots, c_{r_{i}}\) 是否良好。

分析

对于一个数组

如果存在相同长度的另一个数组

使得两个数组之和相同

另一个数组每个位置都与当前数组的不同

而且另一个数组的每个位置都是大于0 的

贪心的来想

就让之前大于1 的地方全都是1

把之前是1的地方都填上2

剩余量大于等于0就行

code

Submission #247922876 - Codeforces

// Codeforces - Educational Codeforces Round 162 (Rated for Div. 2) C. Find B
// https://codeforces.com/contest/1923/problem/C 2024-02-23 22:52:06

#include <bits/stdc++.h>
using namespace std;
#define all(a) begin(a), end(a)
#define int long long
int n, q;
void solve() {
  cin >> n >> q;
  vector<int> c(n + 1), s(n + 1), cnt1(n + 1);
  for (int i = 1; i <= n; i++) cin >> c[i];
  for (int i = 1; i <= n; i++) s[i] = s[i - 1] + c[i];
  for (int i = 1; i <= n; i++) cnt1[i] = cnt1[i - 1] + (c[i] == 1);
  while (q--) {
    int l, r;
    cin >> l >> r;
    if (l == r) {
      cout << "NO\n";
      continue;
    }
    int res = s[r] - s[l - 1] - cnt1[r] + cnt1[l - 1] - r + l - 1;
    if (res >= 0)
      cout << "YES\n";
    else
      cout << "NO\n";
  }
}
signed main() {
  ios::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  int __;
  cin >> __;
  while (__--) solve();
  return 0;
}

D. Slimes[二分]

\(n\) 个史莱姆排成一行。史莱姆按从左到右的顺序编号为 \(1\)\(n\) 。第 \(i\) 个史莱姆的大小为 \(a_i\)

每一秒,都会发生以下情况:恰好一个史莱姆会吃掉它的邻居之一,并根据被吃掉的邻居的大小来增加其大小。只有当史莱姆严格大于邻居时,它才能吃掉它的邻居。如果没有严格大于其邻居之一的史莱姆,则该过程结束。

例如,假设 \(n = 5\)\(a = [2, 2, 3, 1, 4]\) 。该过程可以如下进行:

  • 首先,第 \(3\) 个史莱姆吃掉了第 \(2\) 个史莱姆。第 \(3\) 个史莱姆的大小变为 \(5\) ,第 \(2\) 个史莱姆被吃掉。
  • 然后, \(3\) -rd 史莱姆吃掉 \(1\) -st 史莱姆(它们是邻居,因为 \(2\) -nd 史莱姆已经被吃掉了)。 \(3\) -rd 史莱姆的大小变为 \(7\)\(1\) -st 史莱姆被吃掉。
  • 然后,第 \(5\) 个史莱姆吃掉第 \(4\) 个史莱姆。第 \(5\) 个史莱姆的大小变成了 \(5\) ,第 \(4\) 个史莱姆被吃掉。
  • 然后,第 \(3\) 个史莱姆吃掉第 \(5\) 个史莱姆(它们是邻居,因为第 \(4\) 个史莱姆已经被吃掉了)。第 \(3\) 个史莱姆的大小变成了 \(12\) ,第 \(5\) 个史莱姆被吃掉。

对于每个史莱姆,计算该史莱姆被另一个史莱姆吃掉所需的最短秒数(在该过程的所有可能方式中),或者报告这是不可能的。

分析

先处理能否在一开始被身边的吃掉

找到最近的未被吃掉的

否则的话就二分离当前位置的距离

由于相同大小的 是不能互相吞噬的

所以我们把大小看成颜色

维护每个颜色的左右端点

二分的时候跳过相邻的颜色段

当有两个及以上的颜色段,这两段区间便可以合成一个很大的 怪物

code

Submission #247951109 - Codeforces

// Codeforces - Educational Codeforces Round 162 (Rated for Div. 2) D. Slimes
// https://codeforces.com/contest/1923/problem/D 2024-02-23 23:18:47

#include <bits/stdc++.h>
using namespace std;
#define all(a) begin(a), end(a)
#define int long long
int n, m;
void solve() {
  cin >> n;
  vector<int> a(n + 1), L(n + 1), R(n + 1);
  for (int i = 1; i <= n; i++) cin >> a[i];
  vector<int> ans(n + 1, n + 1);
  for (int i = 1; i <= n; i++) {
    if (i < n)
      if (a[i + 1] > a[i]) ans[i] = 1;
    if (i > 1)
      if (a[i - 1] > a[i]) ans[i] = 1;
  }
  for (int i = 1; i <= n; i++) L[i] = R[i] = i;
  for (int i = 2; i <= n; i++)
    if (a[i] == a[i - 1]) L[i] = L[i - 1];
  for (int i = n - 1; i >= 1; i--)
    if (a[i] == a[i + 1]) R[i] = R[i + 1];
  vector<int> s(n + 1);
  for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
  for (int i = 1; i <= n; i++) {
    if (ans[i] == n + 1) {
      if (i < n) {
        int l = R[i + 1] + 1LL, r = n;
        while (l <= r) {
          int mid = l + r >> 1;
          if (s[mid] - s[i] > a[i])
            ans[i] = min(ans[i], mid - i), r = mid - 1;
          else
            l = mid + 1;
        }
      }
      if (i > 1) {
        int l = 1, r = L[i - 1] - 1LL;
        while (l <= r) {
          int mid = l + r >> 1;
          if (s[i - 1] - s[mid - 1] > a[i])
            ans[i] = min(ans[i], i - mid), l = mid + 1;
          else
            r = mid - 1;
        }
      }
    }
  }
  for (auto &i : ans)
    if (i == n + 1) i = -1;
  for (int i = 1; i <= n; i++) cout << ans[i] << " \n"[i == n];
}
signed main() {
  ios::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  int __;
  cin >> __;
  while (__--) solve();
  return 0;
}

E. Count Paths[启发式合并]

给您一棵树,由 \(n\) 个顶点组成,编号从 \(1\)\(n\) 。每个顶点都涂有某种颜色,用从 \(1\)\(n\) 的整数表示。

如果满足以下条件,则树的简单路径称为美丽路径:

  • 它至少由 \(2\) 个顶点组成;
  • 路径的第一个和最后一个顶点具有相同的颜色;
  • 路径上没有其他顶点与第一个顶点具有相同的颜色。

数数树上美丽简单的路径有多少条。请注意,路径被视为无向(即从 \(x\)\(y\) 的路径与从 \(y\)\(x\) 的路径相同)。

分析

我们可以知道答案有两种

一种是父子链上的选择

另一种是一个节点的不同孩子之间

对于第一种我们需要维护 子树中有没有这个父亲的颜色

对于第二种我们需要维护 子树中有多少链有 相同的颜色

于是我们可以用map<int,int>来维护每一种颜色

但是直接维护是\(O(n^2)\)的显然不可以接受

但是有一种奇妙的东西叫做启发式合并

简而言之就是在合并信息的时候,将\(size\)较小的合并到\(size\)较大的中

可以发现每一次合并对于任意一个移动的点,所在的区间的大小都会扩增至少\(2\)

所以可以在\(O(log_2n)\)次移动中完成维护

时间复杂度的上界是\(O(nlog^2n)\)

code

codeforces.com/contest/1923/submission/248062684

// Codeforces - Educational Codeforces Round 162 (Rated for Div. 2) E. Count
// Paths https://codeforces.com/contest/1923/problem/E 2024-02-23 23:57:48

#include <bits/stdc++.h>
using namespace std;
#define all(a) begin(a), end(a)
#define int long long
int n, m;
void solve() {
  cin >> n;
  vector<int> col(n);
  for (auto &i : col) cin >> i;
  vector<vector<int>> G(n);
  for (int i = 1; i < n; i++) {
    int x, y;
    cin >> x >> y;
    x--, y--;
    G[x].emplace_back(y);
    G[y].emplace_back(x);
  }
  vector<map<int, int>> mp(n);
  int ans = 0;
  auto merge = [&](int i, int j) -> void {
    if (mp[i].size() < mp[j].size()) swap(mp[i], mp[j]);
    for (auto [x, y] : mp[j]) mp[i][x] += y;
  };
  auto dfs = [&](auto self, int u, int fa) -> void {
    mp[u][col[u]]++;
    for (auto it : G[u]) {
      if (it == fa) continue;
      self(self, it, u);
      if (mp[it].contains(col[u]))
        ans += (mp[it][col[u]] * mp[it][col[u]] - mp[it][col[u]]) / 2LL;
      merge(u, it);
    }
    ans += mp[u][col[u]] - 1;
    mp[u][col[u]] = 1;
  };
  dfs(dfs, 0, 0);
  for (auto [x, y] : mp[0]) ans += (y * y - y) / 2LL;
  cout << ans << "\n";
}

signed main() {
  ios::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  int __;
  cin >> __;
  while (__--) solve();
  return 0;
}
posted @ 2024-02-25 01:19  liangqianxing  阅读(60)  评论(1)    收藏  举报