ABC390 DEF 题解
ABC390 DEF 题解
被 D 和 F 创似了。
D
看数据范围,显然搜索。但直接按照题目给出的操作过程来搜索,代码不好实现,且时间复杂度也没有保证。我们可以把题目转化成:把集合 \(\{1, 2, \cdots, N\}\) 划分成若干个不交的集合,每种划分方式对应一个权值(即每个子集的和的异或和),求不同的权值的数量。因此枚举集合的划分方式即可。
代码实现上,用 dfs 枚举划分方式,用一个数组记录每个子集当前的和:
void dfs(int cur, int k, i64 sum) { // 枚举到第 cur 个数,划分出 k 个子集
if(cur == n + 1) {
s.insert(sum);
return;
}
for(int i = 1; i <= k + 1; i++) {
i64 nxt = sum ^ val[i] ^ (val[i] + a[cur]);
val[i] += a[cur];
if(i == k + 1) { // 划分到新的子集
dfs(cur + 1, k + 1, nxt);
} else { // 划分到原有的子集
dfs(cur + 1, k, nxt);
}
val[i] -= a[cur];
}
}
时间复杂度分析:
我们要做两件事,第一件事是计算集合划分的数量,第二件事是证明每种划分方式只会被访问一遍。
-
关于集合划分的数量,数学上早有研究,大小为 \(n\) 的集合的划分数量被记为贝尔数 \(B_n\)。
贝尔数满足递推公式:
\[B_{n + 1} = \sum_{i = 0}^{n} \dbinom{n}{i} B_{i} \]这个公式在 OI wiki 上有足够清晰的证明,这里不再赘述。
本题中,当 \(N = 12\) 时,\(B_N\) 大约为 \(4 \times 10^6\),完全可以通过。(实际上,即使不知道这个公式,也可以搜一发试试。)
-
证明每种划分方式只会被访问一遍:
在集合的划分中,各子集是无序的,即 \(\{\{a_1, a_3\}, \{a_2\}\}\) 和 \(\{\{a_2\}, \{a_1, a_3\}\}\) 是同一个划分,但是是同一划分的不同“表示方法”。我们希望每种划分只被访问一次,也就是要保证对于一个划分,它的所有表示方法中,只有一个被访问。
上文的 dfs 代码实际上保证了:每个子集的下标最小元素的下标升序排列。(在 \(\{\{a_1, a_3\}, \{a_2\}\}\) 这个例子中:第一个子集的下标最小元素为 \(a_1\),第二个子集为 \(a_2\),它们的下标确实升序排列。)显然,对于任意一个划分,只有一种表示方法使得子集的最小下标升序排列,因此我们证明了每个划分只会被访问一次。
实际上这是保证 dfs 时间复杂度的常用方法:枚举无序的元素组合时,我们常常钦定一个特定的顺序,使得每个元素组合只被访问一次。例如枚举正整数的拆分时,我们通常钦定拆分出的数单调不降,这就保证了每种拆分方式只会被访问一次。
总结:转成枚举集合划分的转化想到了,但是居然不知道怎么搜……现在看来怎么这么简单的搜索都不会呢?得加训了
E
用三个背包分别记录花费为 \(i\) 时,能获取多少维生素 \(1/2/3\)。然后二分答案即可。
F
诈骗题!
首先有个显然的转化:\(f(L, R)\) 即为 \([L, R]\) 内值域连续段的个数。然后我想了半天扫描线怎么做,拼尽全力无法战胜。
做不出来的时候考虑转换计数对象。原本我们要统计所有区间的权值和,现在我们转化成:对于每个值 \(c\),统计在多少个区间中,\(c\) 是一个连续段的开头,记为 \(g(c)\),则答案为 \(\sum_{i = 1}^{n} g(i)\)。
在一个区间中,如果 \(c\) 是一个连续段的开头,那么这个区间满足:\(c\) 在此区间中且 \(c - 1\) 不在此区间中。
这样转化之后,统计一下每个值的出现的位置集合,然后直接算就行了。
G
不会多项式