Codeforces Deltix Round Summer 2021 F Sports Betting
个点, 点权为 . 两点之间有 的概率从点 向点 连一条有向边(否则即有 的概率 ). 即根据概率建一个竞赛图. 求能到达其他所有点的点的期望个数.
点数很少, 所以尝试点集为 , 事件 表示有且仅有 中的点能到达所有点, 直接根据期望的定义去计算答案, 应该为 .
现在考虑 如何计算. 首先我们可以发现 一定是一个强连通分量, 且有 (之后用 表示). 简单考虑一下后面这个条件的必要性: 若 , 若 中有其他点, 则要么 , 这种情况 能和 一起组成 SCC, 与 的定义矛盾; 若 , 反过来考虑, 也会有 可能与 的 SCC 组成 SCC. 与 矛盾, 若不然, 则仅有 , 与 矛盾.
点对之间的连边情况是独立的, 所以 之间的连边无论怎样也不影响 , 所以 . 很好算, 先放一边. 考虑 怎么算.
正着很不好做, 考虑做反面, 用 减去所有不是 SCC 的连法即可. 什么样的情况不是 SCC 呢? 根据之前的讨论, 若存在 , 且 是 SCC, 则 不是 SCC. 所以我们枚举 . 发现可以发现, , 所以
整理一下, 就可以得到
事实上还存在一种等效的写法, 这种写法不需要除法, 复杂度可以少一个逆元的 .
把上面的式子接着展开:
后面一项对应的事件其实是: 是 SCC, , , 这个事件等价于: 是SCC, , .
所以可以写成这样:
我们可以通过枚举子集的方法来进行转移 , 边界条件是 . 若 暴力算, 那么总复杂度将会是 , 大概 , 刚好能过.
其实可以优化, 核心就是预处理 .
一个很显然的思路是预处理 , 然后在转移的时候 调用, 那么转移的复杂度为 . 预处理需要枚举两个集合, 时间复杂度是 . 实际上 , 所以只需要枚举子集, 时间复杂度 , 没变, 笑死, 因为转移实际上就是在枚举子集啊.
预处理其实是想干这么一件事: 用空间换时间. 既然换成 对预处理的时间贡献太大(实际上空间复杂度也太高了), 那我换成更大一点的时间, 比如 , 让预处理的时间减小下来. 根据独立事件概率的计算, 设 . 那么转移的时候就多一个 , 预处理的复杂度降为 , 总复杂度为 ( 是线性预处理逆元).
这里还有一个更妙妙的处理方法. 还是根据独立事件的概率乘积. 既然 个对 个不行, 就 个对 个. 具体来说, 可以开一个数组 表示, , 而第一第二维的 分别表示的是 和 是"哪一半"或者"哪一个“中的点. 这样转移复杂度只会乘上一个常数 , 而预处理的复杂度为 . 总复杂度为 . 这应该是最优的解法了.
const int P = 1e9 + 7;
const int MAXN = 20 + 10;
const int MAXS = (1 << 14) + 10;
const int MAXS2 = (1 << 7) + 10;
const int MAXA = 2e6 + 10;
int count(int x) {
int cnt = 0;
while (x) {
cnt++;
x ^= lowbit(x);
}
return cnt;
}
int inv[MAXA];
void init(int n) {
inv[0] = inv[1] = 1;
for (int i = 2; i <= n; i++)
inv[i] = mult(P - P/i, inv[P%i]);
}
int a[MAXN], n, f[MAXS], ans = 0;
int h[2][2][MAXS2][MAXS2], ALL;
int g(int s, int t) {
int HALF = ((1 << 7) - 1);
int sl = s & HALF;
int sh = s >> 7;
int tl = t & HALF;
int th = t >> 7;
int res = 1;
res = mult(res, h[1][1][sh][th]);
res = mult(res, h[1][0][sh][tl]);
res = mult(res, h[0][1][sl][th]);
res = mult(res, h[0][0][sl][tl]);
return res;
}
void calc(int s_is_h, int t_is_h) {
int HALF = ((1 << 7) - 1);
int sup = s_is_h ? (ALL >> 7) : min(HALF, ALL);
int tup = t_is_h ? (ALL >> 7) : min(HALF, ALL);
for (int s = 0; s <= sup; s++) {
int s_state = s << (s_is_h ? 7 : 0);
for (int t = 0; t <= tup; t++) {
int t_state = t << (t_is_h ? 7 : 0);
if (s_state & t_state)
h[s_is_h][t_is_h][s][t] = 0;
else {
int &res = h[s_is_h][t_is_h][s][t];
res = 1;
for (int i = 0; i < n; i++) if (s_state & (1 << i))
for (int j = 0; j < n; j++) if (t_state & (1 << j))
res = mult(res, mult(a[i], inv[a[i]+a[j]]));
}
}
}
}
int main() {
init(2e6);
scanf("%d", &n);
ALL = (1 << n) - 1;
for (int i = 0; i < n; i++)
scanf("%d", a + i);
calc(0, 0); calc(0, 1); calc(1, 0); calc(1, 1);
for (int s = 1; s <= ALL; s++) {
int sub = 0, Us = (~s) & ALL;
for (int s0 = (s-1) & s; s0; s0 = (s0-1) & s) {
int Ss0 = (~s0) & s;
sub = pls(sub, mult(f[s0], g(Ss0, Us)));
}
f[s] = pls(g(s, Us), -sub);
ans = pls(ans, mult(f[s], count(s)));
}
printf("%d\n", ans);
return 0;
}
预览: