NOIP 2002 提高组算法题解

作者: | 更新日期:

涉及贪心、BFS搜索、数学公式、搜索剪枝等算法

本文首发于公众号:天空的代码世界,微信号:tiankonguse

零、背景

今天继续分享 2002 年 NOIP 提高组题解。

一、均分纸牌

题意:N 个盒子摆成一排,盒子里有若干张牌。
每次可以选择一个盒子,选择若干张牌放到相邻的其中一个盒子里。
问最少操作多少次,可以使得所有盒子的牌数量相等。

思路:贪心

先计算出最终每个盒子的牌的数量,然后从第一个盒子开始贪心。
如果盒子的牌少于目标数量,则需要从下个盒子移动对应的牌过来。
如果盒子的牌多于目标数量,则需要移动若干牌到下个盒子里。
如果盒子的牌等于目标数量,则不需要操作。

int avg = sum / n;
int ans = 0;
for (int i = 0; i < n; i++) {
  if (nums[i] == avg) continue;
  ans++;
  nums[i + 1] += nums[i] - avg;
}
printf("%d\n", ans);

二、字串变换

题意:给若干字符串二元组,代表第一个子串可以替换为第二个子串。
问字符串 A 是否可以在 10 步之内替换成字符串 B。

思路:暴力 BFS 搜索

复杂度:O(能过)


int Bfs() {
  if (S == T) return 0;
  unordered_map<string, int> H;  // H[s] 组成 s 的最小步数
  queue<string> que;
  int ans = -1;
  auto Add = [&](const string& s, const int step) {
    if (H.count(s)) return;
    if (s == T) {
      ans = step;
      return;
    }
    if (step < 10) {
      H[s] = step;
      que.push(s);
    }
  };

  Add(S, 0);
  while (!que.empty()) {
    const auto s = que.front();
    que.pop();
    const int step = H[s];
    const int sn = s.size();
    for (const auto& [A, B] : input) {
      const int an = A.size();
      for (int i = 0; i + an - 1 < sn; i++) {
        if (strncmp(&s[i], A.data(), an) == 0) {
          Add(s.substr(0, i) + B + s.substr(i + an), step + 1);
          if (ans != -1) return ans;
        }
      }
    }
  }
  return ans;
}

三、自由落体

题意:高度为 H 的天花板上在数轴 0,1,2,…,n-1 分别有一个小球。
所有小球同时自由下落。
在 S 有一个宽为 L,高为 K 小车,以固定速度 V 朝 0 点前进。
问小车可以接住多少个小球。
自由落体公式:d = 0.5 * g * t^2,其中 g=10

思路:枚举或数学公式。

枚举,就是枚举每一个小球,判断降落到高度为 K,以及降落到地面的时间区间,是否在宽为 L 的车内。

bool Check(int p) {
  double t0 = sqrt(2 * (H - K) / g);
  double t1 = sqrt(2 * H / g);
  double S0 = S - t0 * V;
  double S1 = S - t1 * V;
  if (S0 + L >= p - eps4 && S1 <= p + eps4) {
    return true;
  }
  return false;
}

数学公式,就是列方程。

方程1:小球 i 高度为 K 时,车尾的坐标大于等于 i。
方程2:小球 i 高度为 0 时,车头的坐标小于等于 i。

注意事项1:车尾向下取整,例如 4.9 只能覆盖 4,不能覆盖 5。
注意事项2:车头向上取整,例如 1.1 只能覆盖 2,不能覆盖 1。
注意事项3:求的范围是 [0,n-1]

const double t0 = sqrt(2 * (H - K) / g);
const double t1 = sqrt(2 * H / g);
const double S0 = S - t0 * V;
const double S1 = S - t1 * V;

// p0 向下取整, p1 向上取整
int p0 = floor(S0 + L + eps4);
p0 = min(p0, n - 1);
int p1 = ceil(S1 - eps4);
p1 = max(p1, 0);

printf("%d\n", max(p0 - p1 + 1, 0));

四、矩形覆盖

题意:二维坐标上有 n 个点,使用 k 个没有重叠的矩形覆盖这些点,求所有矩形的最小面积和。

思路:搜索

搜索第 i 个点属于第 k 个矩形,然后求面积之和。
复杂度:k^n

剪枝:一个点加入第 j 个矩形后,这个矩形不能和其他矩形有重叠。
每个矩形维护一个上下左右的边界,从而可以 O(1) 判断。

五、最后

这次比赛出得不好。
第二题没有正确的解法,只能暴力搜索。
第四题也只能暴力加剪枝,也不确定是否可以通过。

《完》

-EOF-

本文公众号:天空的代码世界
个人微信号:tiankonguse
公众号 ID:tiankonguse-code

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越