题解 【UR #12】密码锁

link

竞赛图dp。

首先考虑竞赛图缩点后成为一条链,每一个点向后面的所有点连边。

所以一个竞赛图强连通分量个数的就等于所有点的边都向外连的点集个数。

根据期望线性性,答案就等于每个点集里面的点全都向外连的概率的和,也就是 \(\sum_S P(S)\)\(P(S)\)\(S\) 中点全部向外连的概率。

暴力枚举点集,复杂度 \(O(2^n m)\) ,不能通过。

考虑优化,我们发现 \(m\) 的数量非常小,于是我们想办法把指数变为 \(m\)

注意到每个连通块全部向外连的概率是 \(P(S)=\prod_{u\in S}\prod_{v\not\in S } val(u,v)\) ,其中的 \(val\) 仅题目中给出的边不为 \(\frac{1}{2}\)。那么我们可以单独计算特殊边权,让他的概率乘 \(2\) ,最后把所有边权都乘上 \(\frac{1}{2}\) 就是答案。

因为连通块内最多只有 \(O(m)\) 个点,于是我们可以考虑对每个特殊边形成的连通块枚举点集,算出每种点集大小的概率和,最后再合并,合并的时候用一个背包 dp 转移一下就可以了。

\(\sf{Code}\)

#include<bits/stdc++.h>
#define N 2001001
#define MAX 2001
using namespace std;
typedef long long ll;
typedef double db;
const ll mod=998244353,inf=1e18,inv2=(mod+1)/2;
const db eps=1e-9;
inline void read(ll &ret)
{
	ret=0;char c=getchar();bool pd=false;
	while(!isdigit(c)){pd|=c=='-';c=getchar();}
	while(isdigit(c)){ret=(ret<<1)+(ret<<3)+(c&15);c=getchar();}
	ret=pd?-ret:ret;
	return;
}
ll n,m,inv,dp[N],ans;
ll x,y;
ll val[N];
vector<pair<ll,ll> >v[N];
vector<ll>lis;
inline ll qpow(ll a,ll b)
{
	ll ret=1;
	while(b)
	{
		if(b&1)
			ret*=a,ret%=mod;
		b>>=1;
		a*=a;
		a%=mod;
	}
	return ret;
}
ll f[N];
bool vis[N];
bool flag=true;
inline void dfs(ll ver)
{
	vis[ver]=true;
	lis.push_back(ver);
	for(int i=0;i<v[ver].size();i++)
	{
		ll to=v[ver][i].first;
		if(vis[to])
			continue;
		dfs(to);
	}
	return;
}
bool vst[N];
signed main()
{
	read(n);
	read(m);
	inv=qpow(10000,mod-2);
	for(int i=1;i<=m;i++)
	{
		read(x);
		read(y);
		read(val[i]);
		val[i]*=inv;
		val[i]%=mod;
		v[x].push_back(make_pair(y,val[i]));
		v[y].push_back(make_pair(x,(1-val[i]+mod)%mod));
	}
	dp[0]=1;
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])
		{
			lis.clear();
			for(int j=0;j<=n;j++)
				f[j]=0;
			dfs(i);
			ll maxn=1<<lis.size();
			for(int s=1;s<maxn;s++)
			{
				ll cnt=0;
				for(int j=0;j<lis.size();j++)
				{
					vst[lis[j]]=false;
					if((1<<j)&s)
						vst[lis[j]]=true,cnt++;
				}
				ll res=1;
				for(int j=0;j<lis.size();j++)
				{
					if((1<<j)&s)
					{
						ll ver=lis[j];
						for(int k=0;k<v[ver].size();k++)
						{
							ll to=v[ver][k].first;
							if(!vst[to])
							{
								res*=2*v[ver][k].second%mod;
								res%=mod;
							}
						}
					}
				}
				f[cnt]+=res;
				if(f[cnt]>=mod)
					f[cnt]-=mod;
			}
			for(int j=n;j+1;j--)
				for(int k=0;k<=n;k++)
				{
					dp[j+k]+=dp[j]*f[k]%mod;
					if(dp[j+k]>=mod)
						dp[j+k]%=mod;
				}
		}
	}
	for(int i=1;i<=n;i++)
	{
		ans+=dp[i]*qpow(inv2,i*(n-i))%mod;
		if(ans>=mod)
			ans-=mod;
	}
	printf("%lld\n",ans*qpow(10000,n*(n-1))%mod);
	exit(0);
}

代码很好写,自己做的时候没想到用期望的线性性展开成前缀,这个还是挺妙的。

posted @ 2022-05-05 22:03  CelticOIer  阅读(42)  评论(0编辑  收藏  举报