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];
    }
}

时间复杂度分析

我们要做两件事,第一件事是计算集合划分的数量,第二件事是证明每种划分方式只会被访问一遍。

  1. 关于集合划分的数量,数学上早有研究,大小为 \(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\),完全可以通过。(实际上,即使不知道这个公式,也可以搜一发试试。)

  2. 证明每种划分方式只会被访问一遍:

    在集合的划分中,各子集是无序的,即 \(\{\{a_1, a_3\}, \{a_2\}\}\)\(\{\{a_2\}, \{a_1, a_3\}\}\) 是同一个划分,但是是同一划分的不同“表示方法”。我们希望每种划分只被访问一次,也就是要保证对于一个划分,它的所有表示方法中,只有一个被访问。

    上文的 dfs 代码实际上保证了:每个子集的下标最小元素的下标升序排列。(在 \(\{\{a_1, a_3\}, \{a_2\}\}\) 这个例子中:第一个子集的下标最小元素为 \(a_1\),第二个子集为 \(a_2\),它们的下标确实升序排列。)显然,对于任意一个划分,只有一种表示方法使得子集的最小下标升序排列,因此我们证明了每个划分只会被访问一次。

    实际上这是保证 dfs 时间复杂度的常用方法:枚举无序的元素组合时,我们常常钦定一个特定的顺序,使得每个元素组合只被访问一次。例如枚举正整数的拆分时,我们通常钦定拆分出的数单调不降,这就保证了每种拆分方式只会被访问一次。

AC 记录

总结:转成枚举集合划分的转化想到了,但是居然不知道怎么搜……现在看来怎么这么简单的搜索都不会呢?得加训了

E

用三个背包分别记录花费为 \(i\) 时,能获取多少维生素 \(1/2/3\)。然后二分答案即可。

AC 记录

F

诈骗题!

首先有个显然的转化:\(f(L, R)\) 即为 \([L, R]\) 内值域连续段的个数。然后我想了半天扫描线怎么做,拼尽全力无法战胜。

做不出来的时候考虑转换计数对象。原本我们要统计所有区间的权值和,现在我们转化成:对于每个值 \(c\),统计在多少个区间中,\(c\) 是一个连续段的开头,记为 \(g(c)\),则答案为 \(\sum_{i = 1}^{n} g(i)\)

在一个区间中,如果 \(c\) 是一个连续段的开头,那么这个区间满足:\(c\) 在此区间中且 \(c - 1\) 不在此区间中。

这样转化之后,统计一下每个值的出现的位置集合,然后直接算就行了。

AC 记录

G

不会多项式

posted @ 2025-01-26 12:27  DengStar  阅读(89)  评论(0)    收藏  举报