练习本

第十七届北京信息科技大学程序设计竞赛

比赛🔗

Problem A. 小苯接雨水

把前两大的挡板放在左右两端。

Problem B. 小芳与残骸

模拟几个样例,发现答案就是 $2^{n - 1}$

Problem C. 小苯的棋盘游戏

发现如果行数和列数有一个是奇数,那么一定存在解。

Problem D. 暴暴龙的防奶龙要塞

多画图,我们发现当 $n \le 4$ 的时候无解。当 $n \ge 5$ 时,可以这样构造:中间一个点,点的两边分别和两个环通过两条边进行连接。

最小的环其实就是一条边。因此构造方案就是:(1,2) 连接一条边。[4,n]构成一个环。3 作为中间点,和上面每个环分别连接两条边。

Problem E. 奶龙与奥利奥自动机

组合数学题目。

设 $num_k$ 代表使用 $k$ 次操作,能够产生的非空字符串的数量。 $a_k$ 代表使用 $k$ 次操作,产生的非空字符串中,结尾是 0 的字符串的数量。 $b_k$ 代表使用 $k$ 次操作,产生的非空字符串中,结尾是 $1$ 的字符串的数量。 $c_k$ 代表使用 $k$ 次操作,产生的非空字符串中,结尾是 $01$ 的字符串的数量。

我们有这样的递推关系: $num_k = a_k + b_k + c_k$ , $a_k = c_k = num_{k - 1}$ , $b_k = b_{k - 1} + c_{k - 1}$

根据上面的关系,可以得到 $num_k = 3num_{k - 1} - num_{k - 2}$

答案就是 $\sum_{k = 0}^{n} num_{k}$

初始状态下, $num_0 = 1$ , $num_1 = 3$ 。其中 $num_0 = 1$ 代表的含义其实是空字符串,0 次操作只有一种情况,把它作为特殊情况处理。

void solve() {
  int n;
  cin >> n;
  ll ans = 0;
  ll a = 1, b = 3;
  ans += a + b;

  For1(i, 2, n) {
    ll c = (3 * b - a + MOD1) % MOD1;
    a = b;
    b = c;
    ans = (ans + b) % MOD1;
  }
  cout << ans << '\n';
}

Problem F. 奶龙智斗暴暴龙

猜到采用极端分配的方式比较优。

在桶 A 中放置 1 个白球,在桶 B 中放置 $n-1$ 个白球和 $n$ 个黑球。此时如果选择桶 A,那么取到白球的概率是 1. 如果选择桶 B,那么取到白球的概率是 $\frac{n-1}{2n-1}$

最终答案是 $\frac{3n-2}{4n-2}$

Problem G. 小红的抛物线

把三个点按照横坐标排序,设三个点是 $a, b, c$ ,从 $b$ 做 $x$ 轴的垂线,交线段 $a, c$ 于点 $d$ ,判断点 $d$ 和点 $b$ 的 Y 坐标的大小即可。

Problem H. 小苯的序列染色

每个点可能向左扩展,也可能向右扩展,也就是每个点最多对应 2 个区间。一共有 $2n$ 个区间。问题转化成从这些区间中选出最少的区间,使得这些区间能够覆盖 $[1, n]$

这可以贪心:先按照区间的左端点从小到大排序。维护当前能够覆盖的最远距离 cur_end,如果当前区间的左端点 l <= cur_end + 1, 说明当前区间能够接上之前一段。那么从能够接上的这些区间中,选择一个右端点 $r$ 最大的。如果找不到能够接上的区间,直接返回无解。

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

  VI a(n + 1);
  For1(i, 1, n) cin >> a[i];

  vector<PII> b;
  For1(i, 1, n) {
    int l = i - a[i] + 1, r = i + a[i] - 1;
    if (l >= 1 && l <= n && a[i] >= a[l]) {
      b.push_back({l, i});
    }
    if (r <= n && a[i] >= a[r]) {
      b.push_back({i, r});
    }
  }
  sort(all(b), [&](PII x, PII y) {
    if (x.f1 != y.f1)
      return x.f1 < y.f1;
    return x.f2 > y.f2;
  });

  if (b.empty()) {
    cout << "-1\n";
    return;
  }

  int cur_end = 0, len = SZ(b), ans = 0, nx_end = 0;
  int i = 0;
  while (cur_end < n) {
    while (i < len && b[i].f1 <= cur_end + 1) {
      ckmax(nx_end, b[i].f2);
      i++;
    }

    if (nx_end == cur_end) {
      cout << "-1\n";
      return;
    }
    ans++;
    cur_end = nx_end;
  }
  if (cur_end == n)
    cout << ans << '\n';
  else
    cout << "-1\n";
}

Problem I. 小苯的字符串构造

题目意思比较难懂。

其实就是要求长度是奇数的子字符串,出现次数都是奇数。对偶数长度的字符串没有要求。

模拟几个样例发现,如果 $n$ 是奇数,那么我们可以构造全部是 $a$ 的字符串。这恰好符合要求。如果 $n$ 是偶数,我们只需要添加一个字符 $b$ 即可。

Problem J. 小苯的运算式

设状态 $d_{i, 0}$ 表示只考虑前 $i$ 个元素,最后一个数字的符号是负号的最大值。状态 $d_{i, 1}$ 表示只考虑前 $i$ 个元素,最后一个数字的符号是正号的最大值。这是一个线性 DP

Problem K. 小苯的闯关游戏

注意到如果初始战力越大,到最后越容易满足条件。也就是有单调的性质,因此我们可以对答案二分。

Problem L. 小苯的序列还原

考虑最后一次操作,我们可以直接确定答案数组的最后一个位置 $b_n$ ,它的位置再也不会发生改变了。

根据这个想法,我们可以依次确定 $b_{n - 1}, b_{n - 2}, \ldots$

我们只需要维护当前需要考虑的区间 $[l, r]$ 和当前的翻转次数是奇数还是偶数。

Problem M. GPA Calculator

模拟。