给自己的博客引流:3.15 解除密码 这个是这篇中最认真写的题。
妙妙题!!!太牛了。
首先,\(x_i\in [0,1]\),可以有两种:\(x_i<0.5,x_i\ge 0.5\)。因为在 \([0,1]\) 中抽出 \(0.5\) 的几率为 \(0\),就可以分成 \(x_i<0.5,x_i> 0.5\)。如果这样分,那么 \(x_i,x_j<0.5\implies x_i+x_j\le 1\),\(x_i,x_j>0.5\implies x_i+x_j\ge 1\)。因此我们只需要考虑 \(x_i<0.5,x_j>0.5\) 或者反过来的情况。为了方便起见,设 \(<0.5\) 为白色的,\(>0.5\) 为黑色的,默认 \(x_i\) 白色,\(x_j\) 黑色。
考虑一个白点和一个黑点怎么加起来 \(\le 1\)。几个解释是,黑色的到 \(1\) 的距离比白色的值大,即 \(1-x_j>x_i\)。如果加起来 \(\ge 1\),则 \(1-x_j<x_i\)。(如果 \(x_i>x_j\),则反过来)所以,我们可以求出 \(\min(x_i,1-x_i)\) 和 \(\min(x_j,1-x_j)\) 的大小关系。我们可以小的向大的连边,形成一个图。
如果我们不考虑时间复杂度,那么我们想求出的是:
-
把 \(n\) 个点黑白染色。
-
这种染色方案对应的图要是无环的,即有拓扑序。拓扑序是按照 \(\min(x_i,1-x_i)\) 的从小到大排序。
这个比较难求,我们可以反之求一个拓扑序,它对应的可行的黑白染色个数,设为 \(cnt\)。答案就是 \(\frac{cnt}{2^n\times n!}\)。
设 \(dp_{msk}\) 为已经固定好了 \(msk\) 里面包含的数(作为 \(\min(x_i,1-x_i)\) 最小的 \(\texttt{popcnt}(msk)\) 个)的黑白染色方案数。怎么转移呢?我们还可以发现两个性质:
-
如果 \(x_a+x_b\le 1\),则 \(\min(x_a,x_b)<0.5\)。也就是说,固定 \(\max(x_a,x_b)\) 对应的时候,\(\min\) 的已经在他前面了。
-
如果 \(x_a+x_b\ge 1\),则 \(\max(x_a,x_b)>0.5\)。也就是说,固定 \(\min(x_a,x_b)\) 对应的时候,\(\max\) 的已经在他前面了。这是因为,\(\max\) 的 \(\min(x_i,1-x_i)\) 这个值更小。
那么,就很好转移了。具体来说,对于所有 \(j\notin msk\),
-
如果所有关于 \(j\) 的 \(\le 1\) 的限制的另一端都确定了,\(j\) 就可以是黑色。
-
如果所有关于 \(j\) 的 \(\ge 1\) 的限制的另一端都确定了,\(j\) 就可以是白色。
时间复杂度 \(\mathcal{O}(2^nn)\)。
Code
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod = 998244353;
ll pw(ll x,ll y=mod-2){
ll res=1;
while (y){
if (y&1){
res=res*x%mod;
}
x=x*x%mod;
y>>=1;
}
return res;
}
ll msk[2][20],dp[1<<20];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int n,m;
cin>>n>>m;
ll fac=1;
for (ll i=1; i<=n; i++){
fac=fac*i%mod;
}
ll div=pw(2,n)*fac%mod;
for (int i=1; i<=m; i++){
ll t,a,b;
cin>>t>>a>>b;
a--;
b--;
msk[t][a]|=1<<b;
msk[t][b]|=1<<a;
}
dp[0]=1;
for (int i=0; i<(1<<n); i++){
for (int j=0; j<n; j++){
if (!(i>>j&1)){
if ((msk[0][j]|i)==i){
(dp[i^(1<<j)]+=dp[i])%=mod;
}
if ((msk[1][j]|i)==i){
(dp[i^(1<<j)]+=dp[i])%=mod;
}
}
}
}
cout<<dp[(1<<n)-1]*pw(div)%mod<<"\n";
return 0;
}