1 条题解
-
0
F. Friends and Pizza 详细题解
问题理解
有 个披萨(),第 个披萨有 片。有 个朋友(),每个朋友喜欢某些披萨(用字母表示)。
要选择恰好两个朋友邀请。对于每个披萨:
- 若两人都不喜欢 → Monocarp 吃掉全部 片
- 若恰好一人喜欢 → 该朋友吃掉全部 片
- 若两人都喜欢 → 若 为偶数,每人吃 片;若 为奇数,两人争吵(这种方案不计入答案)
要求:计算对于每个 ,有多少种选两个朋友的方式,使得不争吵且 Monocarp 恰好吃掉 片。
核心难点
- 很小(),但 很大()
- 直接枚举所有 对朋友不可行
- 争吵只发生在两人都喜欢某个奇数片披萨时
- Monocarp 吃的片数 = 两人都不喜欢的披萨的片数之和
第一步:用位掩码表示喜欢集合
将 个披萨编号 到 (对应字母 到第 个字母)。
每个朋友的喜欢集合可以用一个 位二进制掩码 表示:第 位为 表示喜欢披萨 。输入中 已按字典序排序,但只需转换成掩码。
设 = 喜欢集合恰好为 的朋友数量。
第二步:争吵条件
设两个朋友的掩码分别为 和 。
他们不争吵当且仅当:不存在一个披萨 ,使得 为奇数,并且 和 都包含 。定义奇数披萨掩码:
则不争吵条件等价于:
即 和 在奇数披萨位上不能同时为 。
第三步:Monocarp 吃的片数
Monocarp 吃的披萨 = 两人都不喜欢的披萨 = 全集去掉 对应的披萨。
设 为全集。
两人都不喜欢的披萨集合为 ,对应的掩码为:Monocarp 吃的片数 = 。
设 (即掩码对应披萨的总片数),则:
其中 。
因此,给定 和 , 由 唯一确定。
第四步:转化为计数问题
设 = 满足 且 的无序对 的数量(注意 和 是掩码,不是朋友索引,需要乘以朋友计数)。
对每个掩码 , 已知,则:
该 对应的答案增加 。
第五步:如何计算 ?
定义 = 喜欢集合为 且奇数披萨位个数为 的朋友数量。
设 ,则 。对每个 ,对 做SOS DP(子集和变换):
表示“所有子集包含在 内且奇数披萨位个数为 的朋友总数”。
第六步:卷积得到对 的计数
对于固定的 和 , 且 与 无交。
设 ,(对称差),则 ,且 。
更简单的做法:固定 ,枚举 且 ,则 ,且 和 由 拆分成两个集合:,,其中 ,,且 可以是空集,且 。
但这会引入 的拆分,不可行( 太大)。
标程使用的巧妙方法
标程计算 :
$$B[k][mask] = \sum_{i+j = k} C_i[mask] \cdot C_j[mask] $$其中 是 SOS DP 后的结果。
然后对 做逆 SOS DP,得到 ,其中 表示“所有无序对 满足 且 ”的数量。
因为 $oddCnt[x] + oddCnt[y] = oddCnt[x \cup y] + oddCnt[x \& y]$,结合不争吵条件 ,所以 。
因此 就是 。
所以 恰好是“无序对 满足 且不争吵”的数量,即 。
第七步:减去 的情况
当前 包含了 的情况(即同一个朋友选两次),但题目要求选两个不同的朋友。
需要减去 的贡献:当 且 时, 自身对 贡献了 ?
不对, 时,,所以 ,且 (不争吵)。此时对 的贡献是 吗?
实际上 时,对应的无序对是 ,但两个朋友必须是不同的,所以应该完全排除。
标程最后用了一个循环减去 的贡献:for(int i = 0; i < (1 << n); i++) { if(i & odd_mask) continue; int sum = 0; for(int j = 0; j < n; j++) if((i >> j) & 1) sum += a[j]; ans[sum] -= cnt[i]; }注意这里减的是 ,不是 。为什么?
因为 中已经包含了 的贡献作为乘积项 的一部分,但我们需要的是无序对且 。最后除以 之前, 的贡献是 (因为 在 中贡献了 ,但除以 后变成 ,减去 是为了修正)。
第八步:最终答案
对每个 ,(注意 是逆 SOS 后的结果)。
计算 ,将 加到 中。最后,所有 除以 (因为每个无序对被计算了两次),输出即可。
复杂度分析
- SOS DP 对每层 ,,,可行
- 卷积部分 ,其中 ,约 ,在 8 秒内可通过(需优化常数)
- 最后遍历 个掩码,
总结
本题的核心技巧:
- 用位掩码表示披萨喜欢集合()
- 用 SOS DP 处理子集统计
- 用卷积合并两个朋友的集合
- 用逆 SOS DP 恢复出并集信息
- 通过奇偶性处理争吵条件
这是典型的“子集卷积 + SOS DP”问题,适用于 较小但 很大的组合计数场景。
- 1
信息
- ID
- 7217
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者