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;	
}
posted @ 2021-11-06 13:35  shrtcl  阅读(147)  评论(0)    收藏  举报