2020 动物园 · 方案二 位或统计加计数公式
数集合并不会引入新特征,答案只看缺失的位数
正文
// CSP-S 2020 复赛 T2 · 动物园(方案二:位或统计 + 计数公式,满分)
// 原题:https://oj.yecheng.tv/p/CSPS2020B
// 题意:同方案一(n, m ≤ 1e6,k ≤ 64,需满分)。
// 思路(一步到位的计数):
// 关键转化:饲料规则只与"特征位是否出现过"有关。设 appear = 所有
// 原有动物特征位的 OR(k 位掩码)。对规则 (p, q):若位 p ∈ appear
// 则饲料 q 必买。而"新动物 S 合法" ⟺ S 中出现的每个位 p 所触发的
// 饲料都已买。注意:饲料 q 是否已买只取决于 appear 是否含"触发它的
// 位"。定义 goodMask = appear 本身?——正确结论(题面保证规则与
// 动物相容):S 合法 ⟺ S ⊆ appear ∪ Safe,其中 Safe = "位 p ∈ Safe
// 当且仅当 p 触发的饲料全部由 appear 触发的饲料覆盖"。
// 更直接的等价(官方口径):合法 S 的集合 = appear 的"扩充闭包",
// 计数 = 2^(k - popcount(appear)) × 2^0 ... 不对——正解:
// S 只要在"触发饲料 ⊆ 已买饲料"意义下合法,而已买饲料由 appear
// 决定 → 枚举 appear 的每个 0 位 p:加入 p 会新增触发饲料 R(p)。
// R(p) ⊆ 已买 ⟺ p 可自由加入。设 freeCnt = 可自由加入的 0 位个数,
// 则合法 S 数 = 2^freeCnt × 2^(受限位的组合) —— 只有当所有"不可
// 自由加入"的 0 位都不能加入时(每条规则要么已由 appear 满足,
// 要么对应位不可用),合法 S = 子集 of {appear 的 1 位} ∪ {free 位}:
// 总数 = 2^(popcount(appear) + freeCnt)。
// 答案 = 2^(该值) − n。
// freeCnt 的计算:0 位 p 可用 ⟺ 触发它的所有饲料 q 都已买;饲料
// 已买 ⟺ 至少一个 appear 中的位触发它。逐规则把 q 挂到 p 上,
// 再检查 p 的所有 q 是否被 appear 中的位触发。
// 复杂度:O(n + m)。
// 易错点:
// 1. 2^64 溢出:freeCnt + ones 可达 64 → 用 unsigned long long 且
// 特判 1ULL << 64 未定义(拆成两次乘或用 __int128 / 特判 64);
// 2. 饲料编号 q ≤ 1e8 无法直接位压 → 用哈希表/排序去重判存在;
// 3. appear 全 1 时答案 = 2^k − n。
#include <cstdio>
#include <algorithm>
using namespace std;
int n, m, k, c;
unsigned long long a[1000005];
unsigned long long rp[1000005], rq[1000005];
unsigned long long boughtList[1000005];
int boughtN = 0;
int main() {
freopen("zoo.in", "r", stdin);
freopen("zoo.out", "w", stdout);
scanf("%d%d%d%d", &n, &m, &k, &c);
unsigned long long appear = 0;
for (int i = 0; i < n; i++) {
scanf("%llu", &a[i]);
appear |= a[i];
}
for (int j = 0; j < m; j++) scanf("%llu%llu", &rp[j], &rq[j]);
// 已买饲料 = appear 含位 p 的规则的 q
for (int j = 0; j < m; j++)
if ((appear >> (rp[j] % 64)) & 1ULL)
boughtList[boughtN++] = rq[j];
sort(boughtList, boughtList + boughtN);
// 对每个 0 位 p:其触发的 q 是否全部已买
int freeCnt = 0;
for (int b = 0; b < k; b++) {
if ((appear >> b) & 1ULL) continue;
bool ok = true;
for (int j = 0; j < m && ok; j++) {
if (rp[j] % 64 != b) continue;
if (!binary_search(boughtList, boughtList + boughtN, rq[j])) ok = false;
}
if (ok) freeCnt++;
}
int ones = 0;
for (int b = 0; b < k; b++) ones += (int)((appear >> b) & 1ULL);
int e = ones + freeCnt; // 合法 S 数 = 2^e(每一位独立可选)
unsigned long long ans;
if (e >= 64) ans = 0ULL - (unsigned long long)n; // 2^64 − n 的无符号回绕恰好正确
else ans = (1ULL << e) - (unsigned long long)n;
printf("%llu\n", ans);
return 0;
}
CSP-S 标程 · 复赛真题的其它内容
- 2019 格雷码 · 方案一 递归构造
- 2019 格雷码 · 方案二 异或公式
- 2020 儒略日 · 方案一 逐天模拟
- 2020 儒略日 · 方案二 分段整块跳
- 2021 廊桥分配 · 方案一 枚举分配数模拟
- 2021 廊桥分配 · 方案二 预处理归属加前缀和
- 2022 假期计划 · 方案一 BFS 加平方枚举
- 2022 假期计划 · 方案二 预处理最佳中转
- 2023 密码锁 · 方案一 全域枚举
- 2023 密码锁 · 方案二 基准候选收敛
- 2024 决斗 · 方案一 排序贪心模拟
- 2024 决斗 · 方案二 桶计数线性扫描
- 2025 社团招新 · 方案一 状态计数DP
- 2025 社团招新 · 方案二 超额排序移人
- 2019 括号树 · 方案一 逐点重算
- 2019 括号树 · 方案二 栈加 DFS 递推
- 2020 动物园 · 方案一 子集枚举
- 2021 括号序列 · 方案一 立方区间 DP
- 2021 括号序列 · 方案二 平方递推
- 2022 策略游戏 · 方案一 暴力扫描
- 2022 策略游戏 · 方案二 ST 表区间极值
- 2023 消消乐 · 方案一 枚举区间加栈
- 2023 消消乐 · 方案二 记忆化递归
- 2024 超速检测 · 方案一 暴力判定
- 2024 超速检测 · 方案二 区间转化加贪心选点
- 2025 道路修复 · 方案一 逐子集重建MST
- 2025 道路修复 · 方案二 预筛MST全局排序
- 2019 树上的数 · 方案一 全排列暴力
- 2019 树上的数 · 方案二 贪心定序加时刻链
- 2020 函数调用 · 方案一 直接模拟
- 2020 函数调用 · 方案二 拓扑序乘子回推
- 2021 回文 · 方案一 环形配对逆向
- 2021 回文 · 方案二 位置表逆向构造
- 2022 星战 · 方案一 重建判定
- 2022 星战 · 方案二 出度计数维护
- 2023 结构体 · 方案一 顺序模拟
- 2023 结构体 · 方案二 统一类型表封装
- 2024 染色 · 方案一 平方 DP
- 2024 染色 · 方案二 last 指针线性 DP
- 2025 谐音替换 · 方案一 逐对逐位置暴力
- 2025 谐音替换 · 方案二 叠串+AC自动机
- 2019 Emiya 家今天的饭 · 方案一 逐列容斥 DP
- 2019 Emiya 家今天的饭 · 方案二 状态折叠
- 2020 贪吃蛇 · 方案一 multiset 模拟
- 2020 贪吃蛇 · 方案二 双端队列停时规律
- 2021 交通规划 · 方案一 Dinic 最小割
- 2021 交通规划 · 方案二 对偶图最短路
- 2022 数据传输 · 方案一 k 为 1 前缀和
- 2022 数据传输 · 方案二 倍增加矩阵
- 2023 种树 · 方案一 按深度贪心
- 2023 种树 · 方案二 堆加合并贪心
- 2024 擂台游戏 · 方案一 逐 K 模拟
- 2024 擂台游戏 · 方案二 倍增分层预处理
- 2025 员工招聘 · 方案一 集合记忆化搜索
- 2025 员工招聘 · 方案二 三维计数DP