2024.12.26 CW 模拟赛

题面 & 题解

T1

算法

二分, 贪心.

思路

假设最终的平均数为 k, 那么我们一定会贪心地将所有 \(< k\) 的数选进去, 然后 \(> k\) 的数肯定会贪心地选若干个最小的.

选择的一定是一段前缀, 具体地, 将数组排序, 枚举一个前缀 \([1, i]\), 假设选择其中的所有数, 通过前缀和计算出它的平均数 avg, 想知道有多少个 \(>\) avg 的数.

T2

算法

差分, 状态压缩, 动态规划.

思路

题目让我们维护区间加操作, 很难不想到数据结构或者差分来解决. (一个相似操作的题目)

考虑对原数组进行差分, 得到数组 \(b\), 其中:

\[b_i = \left \{ \begin{array}{ll} a_i & i = 1 \\ a_i - a_{i - 1} & 2 \le i \le n \end{array} \right. \]

那么区间 \(+ k \leftrightarrow b_l + k,\ b_{r + 1} - k\).

原问题也可以等价于将 \(b_i\) 全部变成 0 所需要花费的最小次数.

操作最多 n 次, 观察样例:
1 1 4 5 1 4
将其进行差分:
1 0 3 1 -4 3
发现了吗? 对于 "3 1 -4" 这 3 个数, 我们可以用 2 次操作将他们消掉!

更一般的, 对于一个集合 \(\mathbb{S}\), 若 \(\sum_{i \in \mathbb{S}} b_i = 0\), 那么我们就可以用 \(\lvert \mathbb{S} \rvert - 1\) 次操作将其消掉.

所以问题又等价于选择尽量多没有交集的集合, 每个集合的权值和为 0.

观察到 \(n \le 23\), 果断考虑状压.

\(f_{\mathbb{S}}\) 表示 \(\mathbb{S}\) 内的数已经被选了, 最多可以凑出多少个集合.

最终答案即为 \(n - f_{2 ^ n - 1}\) (因为每个集合可以少一次操作).

对于转移, 我们考虑将元素一个个地加入集合中, 每当 \(\mathbb{S}\) 的权值和为 0 时, \(f_{\mathbb{S}} + 1\).

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
#include "cstdio" constexpr int N = 25; int n, a[N], f[1 << 24]; void init() { scanf("%d", &n); for (int i = 0; i ^ n; ++i) scanf("%d", a + i); for (int i = n - 2; ~i; --i) a[i + 1] -= a[i]; } void calculate() { int mx = 1 << n; for (int i = 1; i ^ mx; ++i) { long long tmp = 0; for (int j = 0; j ^ n; ++j) if ((i >> j) & 1) { tmp += a[j]; (f[i] < f[i ^ (1 << j)]) and (f[i] = f[i ^ (1 << j)]); } if (!tmp) ++f[i]; } printf("%d", n - f[mx - 1]); } void solve() { init(); calculate(); } int main() { solve(); return 0; }
posted @   Steven1013  阅读(2)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开