题解 [UR #12] 密码锁

传送门

首先有向完全图被称作竞赛图
那么竞赛图的强连通分量缩点后形成了一个 DAG,而且上面的每个点向后面的所有点连边
定义一个“割集”为链上的一个前缀,那么这个割集中的所有点向割集外的所有点连外向边
那么强连通分量数就等于合法的前缀数 +1
于是考虑期望的线性性拆开,强连通分量数的期望可以拆成每个点集是割集的概率 +1
那么有一个 \(O(2^n)\) 的做法是枚举点集,计算其为割集的概率

但是 \(n\) 比较大,考虑优化到只与 \(m\) 有关
考虑先假设所有边定向概率都是 \(0.5\),将这些特殊边单独算
那么对于一个大小为 \(x\) 的点集,如果其中没有任何特殊边则是割集的概率为 \(0.5x(n−x)\)
如果其中存在一条特殊边,设这条边从 \(x\) 连出的概率为 \(p\),那么这个概率要乘上 \(2p\),也就是这个特殊边的贡献
这样就将很巧妙地将特殊边的贡献独立出来了
那么此时发现只有特殊边的端点是有用的,可以将这些点拎出来单独处理,最后和剩下的点背包合并
具体地,可以对每个连通块状压计算 \(f_i\) 为第 \(i\) 个连通块中有 \(i\) 个点在割集 \(S\) 中的概率
\(g_i\) 为全局有有 \(i\) 个点在割集 \(S\) 中的概率
那么 \(m\) 条边最多只影响 \(m+1\) 个点,这部分可以 \(O(2^m)\) 求出
背包合并均摊到点对上可以做到 \(O(n^2)\)
所以整体复杂度 \(O(2^mm+n^2)\)

yysy,我偷懒写的这样一种卷积是错误的:

for (int i=n; ~i; --i)
	for (int j=top; ~j; --j)
		g[i+j]=(g[i+j]+g[i]*f[j])%mod;

这玩意根本不满足卷积的任何性质啊……
我 tm 调了一年

转载一份题解(感觉讲的很好但原文已经没了)

这个完全图随机定向后是一个竞赛图,将它的强连通分量缩点后我们得到的东西类似一条链,每个点往它后面的所有点连边

在这条链上,我们可以把点划分为两个点集\(S,T\)使得没有从\(T\)中点到\(S\)中点的边(\(S,T\)对应到原图中同样满足条件),那么原图强连通分量个数\(=\)缩点后的点数\(=\)划分方案数\(+1\),所以期望下的划分方案数\(+1\)即为答案

\(p_{x,y}\)表示\((x,y)\)被定向成\(x\rightarrow y\)的概率(不算非特殊边),那么划分方案数的期望就是\(\sum\limits_{S\cup T=V}\prod\limits_{x\in S,y\in T}p_{x,y}\),所以当\(m=0\)时答案为\(\sum\limits_{i=1}^{n-1}\binom ni\left(\frac12\right)^{i(n-i)}\)

\(m\neq0\)时,我们先硬点每条边的概率为\(\frac12\),对于题中给出的那些特殊边,若它的概率为\(p\),那么它对概率的贡献为\(2p\)

对每一个特殊边连接的弱连通块,求出\(f_i\)表示有\(i\)个点\(\in S\)的概率,这里直接枚举弱连通块的每个点属于\(S\)还是\(T\)即可,因为有\(m\)条边的连通块最多有\(m+1\)个点,所以枚举的时间复杂度为\(O(2^m)\)

每求完一个弱连通块的\(f\),将它和答案\(g\)卷积即可,最终答案即为\(\sum\limits_{i=1}^{n-1}g_i\left(\frac12\right)^{i(n-i)}\)

所以说要是做过这个题的话一眼秒掉GDOI2018D1T4应该不成问题...


点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 110
#define pb push_back
#define ll long long
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m;
vector<int> to[N];
int sta[N], top, tot;
bool vis[N], ins[N], col[N];
ll fac[N], inv[N], f[N], g[N], h[N], ans;
const ll mod=998244353, inv2=(mod+1)>>1;
struct edge{int u, v, w;}e[N];
inline ll qpow(ll a, ll b) {ll ans=1; for (; b; a=a*a%mod,b>>=1) if (b&1) ans=ans*a%mod; return ans;}
inline ll C(int n, int k) {return n<k?0:fac[n]*inv[k]%mod*inv[n-k]%mod;}

void dfs(int u) {
	vis[u]=ins[u]=1; sta[++top]=u;
	for (auto v:to[u]) if (!vis[v]) dfs(v);
}

signed main()
{
	n=read(); m=read(); tot=n;
	for (int i=1; i<=m; ++i) {
		e[i].u=read(), e[i].v=read(), e[i].w=read()*qpow(10000, mod-2)%mod;
		to[e[i].u].pb(e[i].v); to[e[i].v].pb(e[i].u);
	}
	g[0]=1; fac[0]=fac[1]=1; inv[0]=inv[1]=1;
	for (int i=2; i<=n; ++i) fac[i]=fac[i-1]*i%mod;
	for (int i=2; i<=n; ++i) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	for (int i=2; i<=n; ++i) inv[i]=inv[i]*inv[i-1]%mod;
	for (int i=1; i<=n; ++i) if (!vis[i]) {
		top=0;
		memset(ins, 0, sizeof(ins));
		memset(f, 0, sizeof(f));
		dfs(i);
		int lim=1<<top;
		// cout<<"top: "<<top<<endl;
		for (int s=0; s<lim; ++s) {
			ll pre=1;
			for (int i=1; i<=top; ++i)
				if (s&(1<<(i-1))) col[sta[i]]=1;
				else col[sta[i]]=0;
			for (int i=1; i<=m; ++i)
				if (col[e[i].u]&&!col[e[i].v]) pre=pre*2*e[i].w%mod;
				else if (!col[e[i].u]&&col[e[i].v]) pre=pre*2*(1-e[i].w)%mod;
			int cnt=__builtin_popcount(s);
			// cout<<"cnt: "<<cnt<<' '<<pre<<endl;
			f[cnt]=(f[cnt]+pre)%mod;
		}
		memset(h, 0, sizeof(h));
		// for (int i=n; ~i; --i)
		// 	for (int j=top; ~j; --j)
		// 		g[i+j]=(g[i+j]+g[i]*f[j])%mod;
		for (int i=0; i<=n; ++i)
			for (int j=0; j<=top; ++j)
				h[i+j]=(h[i+j]+g[i]*f[j])%mod;
		for (int i=0; i<=n; ++i) g[i]=h[i];
	}
	// cout<<"g: "; for (int i=0; i<=n; ++i) cout<<g[i]<<' '; cout<<endl;
	for (int i=1; i<n; ++i) ans=(ans+g[i]*qpow(inv2, i*(n-i)))%mod;
	ans=(ans+1)*qpow(10000, n*(n-1))%mod;
	cout<<(ans%mod+mod)%mod<<endl;

	return 0;
}
posted @ 2022-03-17 20:50  Administrator-09  阅读(4)  评论(0编辑  收藏  举报