题解 P7105 【「C.E.L.U-01」门禁】
提供一种干爆 std 的
设
则有套路容斥:考虑这个点集不是连通块时,枚举其中一个点所在的连通块,要求这个连通块自己连通,并且和剩余的点之间没有边。
可以得到转移式:
其中
暴力计算将得到一个
考虑折半:预处理出 4 个数组
- 所有
[0,n/2) 中的点的子集 到 所有[0,n/2) 中的点的子集 之间没有边的概率 - 所有
[0,n/2) 中的点的子集 到 所有[n/2,n) 中的点的子集 之间没有边的概率 - 所有
[n/2,n) 中的点的子集 到 所有[0,n/2) 中的点的子集 之间没有边的概率 - 所有
[n/2,n) 中的点的子集 到 所有[n/2,n) 中的点的子集 之间没有边的概率
这样暴力预处理这 4 个数组的时间是
如此继续暴力执行 DP 即可做到
实战效果:在目前数据范围下最大的测试点可以比 std 快 20 倍左右。
#include <bits/stdc++.h>
using namespace std;
int n, S;
long double s[2][2][1<<7][1<<7], dp[1<<14], p[14][14];
inline void Read() {
cin >> n;
for (int i = 0;i < n;i++) {
for (int j = 0;j < n;j++) cin >> p[i][j];
}
}
inline void Prefix() {
S = (n + 1) / 2;
for (int f1 = 0;f1 < 2;f1++) {
for (int f2 = 0;f2 < 2;f2++) {
for (int i = 0;i < (1 << S);i++) {
for (int j = 0;j < (1 << S);j++) {
s[f1][f2][i][j] = 1;
for (int k = 0;k < S;k++) {
for (int l = 0;l < S;l++) {
if (((i >> k) & 1) && ((j >> l) & 1)) s[f1][f2][i][j] *= 1 - p[k + f1 * S][l + f2 * S];
}
}
}
}
}
}
}
inline long double Query(int s1, int s2) {
return s[1][1][s1 >> S][s2 >> S] * s[1][0][s1 >> S][s2 & ((1 << S) - 1)] * s[0][1][s1 & ((1 << S) - 1)][s2 >> S] * s[0][0][s1 & ((1 << S) - 1)][s2 & ((1 << S) - 1)];
}
inline void Solve() {
for (int i = 1;i < (1 << n);i++) {
dp[i] = 1;
int t = i ^ (i & -i);
for (int j = t;j;j = (j - 1) & t) dp[i] -= dp[i ^ j] * Query(i ^ j, j);
}
long double ans = 0;
for (int i = 1;i < (1 << n);i++) ans += dp[i] * Query(i, ((1 << n) - 1) ^ i);
cout << fixed << setprecision(11) << ans << endl;
}
int main() {
Read();
Prefix();
Solve();
return 0;
}