2024.12.26 CW 模拟赛
T1
算法
二分, 贪心.
思路
假设最终的平均数为 k, 那么我们一定会贪心地将所有 \(< k\) 的数选进去, 然后 \(> k\) 的数肯定会贪心地选若干个最小的.
选择的一定是一段前缀, 具体地, 将数组排序, 枚举一个前缀 \([1, i]\), 假设选择其中的所有数, 通过前缀和计算出它的平均数 avg, 想知道有多少个 \(>\) avg 的数.
T2
算法
差分, 状态压缩, 动态规划.
思路
题目让我们维护区间加操作, 很难不想到数据结构或者差分来解决. (一个相似操作的题目)
考虑对原数组进行差分, 得到数组 \(b\), 其中:
那么区间 \(+ 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
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步