\(\frak{Description}\)

\(\text{Link.}\)

\(\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_{s,i}+g_{s,i}=\binom{\text{cnt}_s}{i} \]

所以通过 \(f\) 可以计算出对应的 \(g\),接下来考虑如何从 \(f\) 递推到 \(g\). 首先一个想法肯定是枚举 \(t\subset s\) 必须连通,然后在 \(s\setminus t\) 内部 任意选边:

\[f_{s,i}=\sum_{t\subset s}\sum_{j=0}^{\min\{\text{cnt}_t,i\}}g_{t,j}\cdot \binom{\text{cnt}_{s\setminus t}}{i-j} \]

但这样忽略了一个问题:现在的 \(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\) 条边后停止):

\[\begin{align} \text{Ans}&=\sum_{k=1}^{m}\frac{k}{m+1}\cdot \left( \frac{f_{U,k-1}}{\binom{\text{cnt}_U}{k-1}}-\frac{f_{U,k}}{\binom{\text{cnt}_U}{k}} \right)\\ &=\frac{1}{m+1}\cdot \left(\sum_{k=0}^{m-1} \frac{f_{U,k}}{\binom{\text{cnt}_U}{k}}-m\cdot \frac{f_{U,m}}{\binom{\text{cnt}_U}{m}}\right) \end{align} \]

再提一嘴,其实后面那一坨 \(m\) 项根本不需要管,根据实地测试 \(f_{U,m}\) 这一项压根就是零,可能是因为数据保证图一定连通。

\(\frak{Code}\)

```cpp #include #define print(x,y) write(x),putchar(y)

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.0
f[lim-1][i]/C[cnt[lim-1]][i];
printf("%.6f\n",ans/(m+1));
return 0;
}

</details>
posted on 2020-04-10 11:04  Oxide  阅读(6)  评论(0编辑  收藏  举报