UOJ181 密码锁 题解
题意
\(n\) 个点的竞赛图,每两个点之间边两种方向概率均为 \(\frac{1}{2}\) ,但有 \(m\) 条边的的两种方向的概率给定了。求期望强联通分量个数。
\(1\le n\le38,1\le m\le19\)
题解
竞赛图缩点后是一条链。可以发现,有且仅有每个前缀能保证,其只有连出去的边。而强联通分量个数恰好等于前缀个数。
暴力的做法是枚举每个点集,计算其所有边均向外连的概率,算贡献。
现在来优化一下。因为很多条边的概率都是 \(\frac{1}{2}\) ,所以对于大小为 \(x\) 的点集,假如和其它点相连的边两种方向概率均为 \(\frac{1}{2}\) ,那么其是一个前缀概率为 \(0.5^{x(n-x)}\) 。
那么现在枚举特殊边集合。这些边都是连接某个点集和其补集的。是个二分图。把点黑白染色之后可以容易算贡献,一条边的贡献只要在原来基础上乘上 \(2p\) 即可。
不过还是过不了。考虑染色只和每个特殊边连通块有关。对每个连通块处理,枚举每个点是黑色还是白色。用背包来合并答案,最后对每个大小算一下贡献就行了。
时间复杂度: \(O(2^{m+1}n)\) 。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N=40,mod=998244353;
int n,m,k,tot,ans,b[N],id[N],u[N],v[N],c[N],f[N][N];
int qpow(int x,int y){
int res=1;
while(y){
if(y&1) res=1ll*res*x%mod;
x=1ll*x*x%mod;y>>=1;
}
return res;
}
void dfs(int x){
b[x]=k;id[x]=tot++;
for(int i=1;i<=m;i++) if(u[i]==x||v[i]==x){
int y=u[i]==x?v[i]:u[i];
if(!b[y]) dfs(y);
}
}
signed main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) scanf("%d%d%d",&u[i],&v[i],&c[i]),c[i]=796898467ll*c[i]%mod;
f[0][0]=1;
for(int i=1;i<=n;i++) if(!b[i]){
k++;tot=0;dfs(i);
for(int j=0;j<(1<<tot);j++){
int p=1,cnt=__builtin_popcount(j);
for(int t=1;t<=m;t++)
if(b[u[t]]==k&&b[v[t]]==k&&((j>>id[u[t]]&1)^(j>>id[v[t]]&1))) p=2ll*p*((j>>id[u[t]]&1)?c[t]:1-c[t]+mod)%mod;
for(int t=cnt;t<=n;t++) (f[k][t]+=1ll*f[k-1][t-cnt]*p%mod)%=mod;
}
}
for(int i=1;i<=n;i++) (ans+=1ll*f[k][i]*qpow((mod+1)/2,i*(n-i))%mod)%=mod;
printf("%lld\n",1ll*ans*qpow(10000,n*(n-1))%mod);
return 0;
}

浙公网安备 33010602011771号