\(\frak{Description}\)
\(\frak{Solution}\)
先开始乱搞了一发随机化,结果连 \(m=n-1\) 的样例都过不去,精度要求六位还是太严厉了 qwq.
考虑暴力,实际上可以枚举每条边的相对大小关系,用 \(\rm kruskal\) 求出最小生成树后,由于我们知道生成树上最大边是整体第 \(k\) 小,所以代入题目提示第 \(k\) 小边的期望值为 \(k/(m+1)\),加入答案即可。
事实上我们不需要将 "确定每条边的相对大小关系" 限制得那么死。重新考虑 \(\rm kruskal\) 的过程,当加入第 \(k\) 条边后如果图连通,那么这张图的贡献就是 \(k/(m+1)\). 所以不妨钦定一个边集 \(S\) 为前 \(|S|\) 小,如果边集加入第 \(k\) 条边时连通,它的贡献就是 \(k/(m+1)\). 我们并不需要知道边的相对大小关系,因为只要算出选择 恰好 \(k\) 条边后停止的边集方案数,再除以选 \(k\) 条边的总方案数,就可以得到概率,乘上 \(k/(m+1)\) 即为贡献。
"恰好" 是不易的,所以考虑容斥:令 \(f_{s,i},g_{s,i}\) 分别为点集为 \(s\),用了 \(i\) 条边,且点集不连通/连通的方案数,\(\text{cnt}_s\) 为点集 \(s\) 内部 的边数。那么显然有(虽然我肯定看不出来:
所以通过 \(f\) 可以计算出对应的 \(g\),接下来考虑如何从 \(f\) 递推到 \(g\). 首先一个想法肯定是枚举 \(t\subset s\) 必须连通,然后在 \(s\setminus t\) 内部 任意选边:
但这样忽略了一个问题:现在的 \(s\setminus t\),可能在之后被枚举成 \(t\),就可能出现算重的情况。解决方法是枚举点 \(p\in t\subset s\),就可以巧妙地规避这个问题。这个 \(\mathtt{dp}\) 的复杂度大概是 \(\mathcal O\big(m^2\cdot \sum 2^{k-1}\cdot \binom{n}{k}\big)=\mathcal O(m^2/2\cdot 3^n)\) 的。不过这个上界很松,所以还是挺快的 /kk.
最终的答案即为(枚举选取 \(k\) 条边后停止):
再提一嘴,其实后面那一坨 \(m\) 项根本不需要管,根据实地测试 \(f_{U,m}\) 这一项压根就是零,可能是因为数据保证图一定连通。
\(\frak{Code}\)
template
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' || s<'0')
f |= (s=='-');
while(s>='0' && s<='9')
x = (x<<1)+(x<<3)+(s^48),
s = getchar();
return f?-x:x;
}
template
inline void write(T x) {
static int writ[50],w_tp=0;
if(x<0) putchar('-'),x=-x;
do writ[++w_tp]=x-x/10*10,x/=10; while(x);
while(putchar(writ[w_tp--]^48),w_tp);
}
include
using namespace std;
int n,m,edge[1<<10],lim,cnt[1<<10];
double f[1<<10][200],g[1<<10][200],C[200][200];
int main() {
n=read(9), m=read(9); lim = (1<<n);
C[0][0]=1;
for(int i=1;i<=m;++i) {
C[i][0] = 1;
++ edge[(1<<read(9)-1)|(1<<read(9)-1)];
for(int j=1;j<=i;++j)
C[i][j] = C[i-1][j]+C[i-1][j-1];
}
for(int s=0;s<lim;++s)
for(int t=s;t;t=(t-1)&s)
cnt[s] += edge[t];
for(int s=0;s<lim;++s) {
int p = s&-s;
for(int i=0;i<=cnt[s];++i) {
for(int t=s;t;t=(t-1)&s) if((t&p)==p) {
for(int j=0, up=min(cnt[t],i); j<=up; ++j)
f[s][i] += g[t][j]C[cnt[s^t]][i-j];
}
g[s][i] = C[cnt[s]][i]-f[s][i];
}
}
double ans=0;
for(int i=0;i<=m;++i)
ans += 1.0f[lim-1][i]/C[cnt[lim-1]][i];
printf("%.6f\n",ans/(m+1));
return 0;
}
</details>