1 条题解
-
0
题目分析
题目要求计算在满足严格递减条件的矩阵排列中,cz_?? 相邻妹子数量的期望。核心在于统计所有合法排列中 cz_?? 相邻妹子的总数,再除以合法排列的总数。由于矩阵规模较小((n+m \leq 20)),可通过状态压缩动态规划(DP)结合组合计数求解。
解题思路
- 状态表示:用二进制状态表示矩阵每列的填充高度,通过DFS预处理所有合法状态。
- DP预处理:
- 正向DP:从最高身高开始填充,统计每个状态下的排列数及相邻妹子的贡献。
- 反向DP:从cz_??的位置反向填充,统计后续排列的贡献。
- 组合计数:结合正向和反向DP结果,计算cz_??相邻妹子的总贡献,最终除以总排列数得到期望。
代码实现(带注释)
#include <algorithm> #include <iostream> #include <cassert> #include <cstring> #include <cstdlib> #include <cstdio> using namespace std; typedef bool boolean; const int N = 20, S = 2e5, M = 105, Mod = 1e9 + 7; const int None = N << 1; // 表示无相邻贡献的状态 // 扩展欧几里得算法求逆元 void exgcd(int a, int b, int &x, int &y) { if (!b) x = 1, y = 0; else { exgcd(b, a % b, y, x); y -= (a / b) * x; } } // 求a在模n下的逆元 int inv(int a, int n) { int x, y; exgcd(a, n, x, y); return (x < 0) ? (x + n) : (x); } // 状态结构体:存储二进制状态、块数、ID typedef class Status { public: int s, block, id; Status() { } Status(int s, int block, int id): s(s), block(block), id(id) { } // 按块数排序 boolean operator < (Status b) const { return block < b.block; } } Status; int n, m, student; pair<double, char> hei[M]; // 存储身高和性别 char ord[M]; // 排序后的性别序列 int id[1 << N]; // 状态到ID的映射 int f[S][N << 1 | 1]; // 正向DP:f[状态][位置] = 排列数/贡献 int g[S][N << 1 | 1]; // 反向DP:g[状态][位置] = 排列数/贡献 int cnts = 0; // 状态总数 Status sord[S]; // 排序后的状态 int ar[N], br[N]; // 辅助数组,存储每列高度 // 将列高度数组编码为二进制状态 int coding(int *ar) { int rt = 0, lim = m; for (int i = 0; i < n; i++) { while (lim > ar[i]) lim--, rt <<= 1; rt = rt << 1 | 1; } return rt << lim; } // 将二进制状态解码为列高度数组 void decode(int *ar, int s) { int lim = 0, i = n; while (i) { while (!(s & 1)) s >>= 1, lim++; ar[--i] = lim; s >>= 1; } } int rk = 0; // cz_??在排序后的位置 char buf[10]; // 初始化:读取输入并排序身高 inline void init() { double hcz; scanf("%d%d%lf", &n, &m, &hcz); student = n * m; // 读取其他学生信息 for (int i = 0; i < student - 1; i++) { scanf("%lf%s", &hei[i].first, buf); hei[i].second = buf[0]; } // 添加cz_??的信息 hei[student - 1] = make_pair(hcz, '.'); // 按身高降序排序 sort(hei, hei + student, greater< pair<double, char>>()); // 记录排序后的性别序列,并找到cz_??的位置 for (int i = 0; i < student; i++) { ord[i] = hei[i].second; if (ord[i] == '.') rk = i; } } // DFS预处理所有合法状态 void dfs(int dep, int last, int sta, int block) { if (dep == n) { // 所有行处理完毕 sta <<= last; // 补全剩余位 id[sta] = cnts; sord[cnts] = Status(sta, block, cnts); cnts++; return; } // 枚举当前行的高度 for (int i = 0; i <= last; i++) dfs(dep + 1, i, sta << (last - i + 1) | 1, block + i); } // 模加法 int add(int a, int b) { a += b; if (a >= Mod) a -= Mod; return a; } // 位置编码:将(x,y)映射为唯一整数 #define pid(_x, _y) (n - (_x) + (_y)) // 检查位置(x,y)与j行的兼容性(正向DP) boolean check1(int *ar, int x, int y, int j) { if (x == n - 1) return true; if (x == j) return ar[x + 1] < y; if (x + 1 == j) return ar[x] == y || (ar[x + 1] + 1 < y); return true; } // 检查位置(x,y)与j行的兼容性(反向DP) boolean check2(int *ar, int x, int y, int j) { if (!x) return true; if (x == j) return ar[x - 1] > y; if (x - 1 == j) return ar[x] == y || (ar[x - 1] - 1 > y); return true; } int tr[N]; // 临时存储状态转移 // 主求解函数 inline void solve() { // 预处理所有合法状态 dfs(0, m, 0, 0); sort(sord, sord + cnts); // 按块数排序 // 初始化正向DP:初始状态(空矩阵)的排列数为1 f[sord[0].id][None] = 1; // 正向DP:从最高身高开始填充 for (int sti = 0; sti < cnts - 1; sti++) { int r = sord[sti].block; // 当前填充的人数 int si = sord[sti].id; // 当前状态ID decode(ar, sord[sti].s); // 解码列高度 memcpy(br, ar, sizeof(ar)); // 复制状态 memset(tr, -1, sizeof(tr)); // 初始化转移数组 // 尝试填充每个位置 for (int i = 0, s; i < n; i++) if (ar[i] < m && (!i || ar[i - 1] > ar[i])) { // 合法填充位置 br[i]++; // 填充当前列 tr[i] = s = id[coding(br)]; // 新状态ID // 更新排列数 f[s][None] = add(f[s][None], f[si][None]); // 如果当前是妹子,记录贡献 if (ord[r] == 'G') f[s][pid(i, br[i])] = add(f[s][pid(i, br[i])], f[si][None]); br[i]--; // 恢复状态 } // 处理相邻贡献的转移 for (int x = 0; x < n; x++) { for (int y = ((x < n - 1) ? (ar[x + 1] + (ar[x + 1] != ar[x])) : (0)); y <= ar[x]; y++) if (ar[x] && y && f[si][pid(x, y)]) for (int j = 0, s; j < n; j++) if ((s = tr[j]) != -1 && check1(ar, x, y, j)) f[s][pid(x, y)] = add(f[s][pid(x, y)], f[si][pid(x, y)]); } } // 反向DP:从cz_??的位置反向填充 int ends = sord[cnts - 1].id; // 填满矩阵的状态ID g[ends][None] = 1; // 初始化反向DP for (int sti = cnts - 1; sti; sti--) { int r = sord[sti].block - 1; // 当前填充的人数(反向) int si = sord[sti].id; // 当前状态ID if (r == rk) break; // 到达cz_??的位置,停止 decode(ar, sord[sti].s); // 解码列高度 memcpy(br, ar, sizeof(ar)); // 复制状态 memset(tr, -1, sizeof(tr)); // 初始化转移数组 // 尝试反向填充每个位置 for (int i = 0, s; i < n; i++) if (ar[i] && (i == n - 1 || ar[i] > ar[i + 1])) { // 合法反向填充位置 br[i]--; // 反向填充 tr[i] = s = id[coding(br)]; // 新状态ID // 更新排列数 g[s][None] = add(g[s][None], g[si][None]); // 如果当前是妹子,记录贡献 if (ord[r] == 'G') g[s][pid(i, br[i])] = add(g[s][pid(i, br[i])], g[si][None]); br[i]++; // 恢复状态 } // 处理相邻贡献的转移 for (int x = 0; x < n; x++) for (int y = ((x) ? (ar[x - 1] - (ar[x - 1] != ar[x])) : (m)); y >= ar[x]; y--) if (ar[x] < m && y < m && g[si][pid(x, y)]) for (int j = 0, s; j < n; j++) if ((s = tr[j]) != -1 && check2(ar, x, y, j)) g[s][pid(x, y)] = add(g[s][pid(x, y)], g[si][pid(x, y)]); } // 计算总贡献 int res = 0; for (int sti = 0; sti < cnts; sti++) { int r = sord[sti].block; // 当前填充人数 if (r == rk + 1) { // 处理cz_??的下一个状态 decode(ar, sord[sti].s); memcpy(br, ar, sizeof(ar)); // 枚举cz_??的相邻位置 for (int i = 0, s; i < n; i++) if (ar[i] && (i == n - 1 || ar[i] > ar[i + 1])) { br[i]--; s = id[coding(br)]; // 统计上方、右侧、左侧、下方的妹子贡献 if (br[i]) res = add(res, f[s][pid(i, br[i])] * 1ll * g[sord[sti].id][None] % Mod); if (ar[i] < m) res = add(res, f[s][None] * 1ll * g[sord[sti].id][pid(i, ar[i])] % Mod); if (i) res = add(res, f[s][pid(i - 1, ar[i])] * 1ll * g[sord[sti].id][None] % Mod); if (i < n) res = add(res, f[s][None] * 1ll * g[sord[sti].id][pid(i + 1, br[i])] % Mod); br[i]++; } } } // 计算期望:总贡献 / 总排列数 res = res * 1ll * inv(f[ends][None], Mod) % Mod; printf("%d\n", res); } int main() { init(); // 初始化输入 solve(); // 求解 return 0; }代码解释
- 状态预处理:通过DFS生成所有合法的矩阵填充状态,并编码为二进制。
- 正向DP:从最高身高开始填充,统计每个状态下的排列数及妹子贡献。
- 反向DP:从填满矩阵的状态反向推导,统计cz_??位置后的贡献。
- 结果计算:结合正反向DP结果,计算cz_??相邻妹子的总贡献,除以总排列数得到期望,并通过逆元处理模运算。
- 1
信息
- ID
- 5653
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者