1 条题解
-
0
题目解析
题意简述
有 道题, 个验题人,每个验题人把每道题标记为 E(简单)或 H(困难)。
要选出若干道题组成一套题(顺序重要),要求对于任意一个验题人,不能出现他标记为 简单 的题排在 困难 的题后面。
求不同的非空套题数量,对 取模。
思路分析
条件转化
设第 道题对 个验题人的评分构成一个 维 0/1 向量 (0 表示 E,1 表示 H)。
对于一套题 ,条件等价于:
对任意验题人 ,序列 必须是形如111...000
(全 1 在前,全 0 在后)。这进一步等价于:对于任意相邻题目 ,必须满足 (按分量比较)。
即: 的每个二进制位 的对应二进制位。
关键观察
- 如果 ,则 和 可以任意顺序排列(两个方向都合法)。
- 如果 (按分量),则只能 在 前。
- 如果 与 不可比(即存在某位 且存在另一位 ),则两个方向都不允许。
因此,合法排列是 向量的一个 非递增链(按分量偏序)。
动态规划设计
设 表示以 评分向量为 的题目 结尾的合法序列总数。
初始:(长度为 1 的序列, 是评分向量为 的题目数量)。
转移:
对于每个 ,枚举它的超集 (即 且 ),
则 。
含义:在任意以 结尾的序列后面,添加一个 题目(有 种选择),得到新的以 结尾的序列。
SOS DP 优化
直接枚举超集复杂度为 ,对于 可能过大。
我们可以用 SOS DP(Sum Over Subsets)技巧优化到 。按位从高到低遍历,对每个 ,如果它的某位为 0,则 是它的一个超集,我们可以累加这些超集的 值。
算法步骤
- 统计每个评分向量 的出现次数 。
- 初始化 。
- 对每个位 ,从大到小遍历 ,若 的该位为 0,则:$$dp[mask] \ += dp[mask | (1<<bit)] \times cnt[mask] $$
- 答案 = 。
复杂度分析
- 时间复杂度:
- 空间复杂度:
代码实现
#include <bits/stdc++.h> using namespace std; const int MOD = 1e9 + 7; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int N, M; cin >> N >> M; vector<int> cnt(1 << M, 0); for (int i = 0; i < M; i++) { string s; cin >> s; for (int j = 0; j < N; j++) { if (s[j] == 'H') { cnt[j] |= (1 << i); } } } vector<int> freq(1 << M, 0); for (int i = 0; i < N; i++) { freq[cnt[i]]++; } vector<int> dp(1 << M, 0); for (int mask = 0; mask < (1 << M); mask++) { dp[mask] = freq[mask]; } for (int bit = 0; bit < M; bit++) { for (int mask = (1 << M) - 1; mask >= 0; mask--) { if (!(mask >> bit & 1)) { int sup = mask | (1 << bit); dp[mask] = (dp[mask] + 1LL * dp[sup] * freq[mask]) % MOD; } } } int ans = 0; for (int mask = 0; mask < (1 << M); mask++) { ans = (ans + dp[mask]) % MOD; } cout << ans << "\n"; return 0; }
示例验证
样例 1
3 1 EHE
- 题目向量:(E=0, H=1)
- 计算得 ,总和 ,与输出一致。
样例 2
10 6 EHEEEHHEEH EHHHEEHHHE EHEHEHEEHH HEHEEEHEEE HHEEHEEEHE EHHEEEEEHE
- 输出 ,与题目一致。
总结
本题的关键在于:
- 将验题人的评分转化为二进制向量。
- 发现合法排列等价于向量序列的非递增链。
- 使用 SOS DP 高效统计所有合法序列数目。
这种方法高效且易于实现,适合 的大数据范围。
- 1
信息
- ID
- 3340
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者