先把求概率转化为求方案数。
考虑消去这个 $1$ 的常数,给所有数减去 $\frac{1}{2}$,于是转化为变量在 $[-\frac{1}{2},\frac{1}{2}]$ 之间随机。
那么两条限制 $x_i + x_j \leq 1$ 和 $x_i + x_j \geq 1$ 就变成了:$x_i + x_j \leq 0, x_i + x_j \geq 0$。
注意到一个很好的性质:$x_i + x_j$ 的正负性只和绝对值大的那个数的正负性有关。
这意味着我们可以从小到大枚举 $|x_i|$ 加入,每次只要保证 $i$ 的限制和之前的加入状态不冲突即可。
怎么实现这个过程呢?记 $leq_i$ 表示需要在 $i$ 之前加入的点的集合。
也就是每次枚举已经加入的集合 $S$ 和下次要加入 $i$,只要满足 $S$ 和 $leq_i$ 的交集为 $leq_i$ 就能保证 $i$ 和之前加入的数不冲突。
这可以通过状压 DP 解决。
怎么求概率呢?用满足条件的方案数除以总情况数即可,答案为 $\frac{dp_{2^n-1}}{n!2^n}$
#include <bits/stdc++.h>
using namespace std;
const int N = 25, V = (1 << 20) + 15, mod = 998244353;
int n, m;
int leq0[N], leq1[N];
int dp[V];
int qmi(int a, int k) {
int res = 1;
while (k) {
if (k & 1) res = res * 1ll * a % mod;
a = a * 1ll * a % mod;
k >>= 1;
}
return res;
}
int main() {
scanf("%d%d", &n, &m);
while (m--) {
int opt, u, v; scanf("%d%d%d", &opt, &u, &v);
if (opt == 0) leq0[u] |= (1 << v - 1), leq0[v] |= (1 << u - 1);
else leq1[u] |= (1 << v - 1), leq1[v] |= (1 << u - 1);
}
dp[0] = 1;
for (int S = 0; S < (1 << n); S++) {
for (int i = 1; i <= n; i++) { //接下来放什么数
if (S & (1 << i - 1)) continue;
if ((S & leq0[i]) == leq0[i]) (dp[S | (1 << i - 1)] += dp[S]) %= mod;
if ((S & leq1[i]) == leq1[i]) (dp[S | (1 << i - 1)] += dp[S]) %= mod;
}
}
int inv = 1;
for (int i = 1; i <= n; i++) inv = inv * 1ll * i % mod;
inv = inv * 1ll * qmi(2, n) % mod;
inv = qmi(inv, mod - 2);
printf("%d\n", dp[(1 << n) - 1] * 1ll * inv % mod);
return 0;
}