BZOJ
每条边都有一个[0,1]边权,求最小生成树中最大边的期望大小
n个[0,1]的随机变量,第k小的期望是 k n + 1 \frac k {n+1} n+1k
姑且不论为啥第k小的期望是 k n + 1 \frac k {n+1} n+1k,那是题目中给的信息……我也不会证qwqqq
MST上的最大边就是在kruskal的时候最后加入的边,那么就要求加到第i条边时联通的概率,则这个的权值是 i m + 1 \frac i {m+1} m+1i
由于这题数据范围比较小,我们可以直接把概率搞成方案数,开个ll存就行。那么可以设,f[s][i]表示用i条边s集合不连通的方案数,g[s][i]表示用i条边s集合联通的方案数,显然它们有这样的关系:
f [ s ] [ i ] + g [ s ] [ i ] = ( c n t [ s ] i ) f[s][i]+g[s][i]=\binom {cnt[s]} {i} f[s][i]+g[s][i]=(icnt[s])
因此我们就只需要维护一个状态即可。转移时我们只需要枚举集合s的一个子集,使得这个子集独立即可。
f [ s ] [ i ] = ∑ t ⊊ s , j ≤ i g [ t ] [ j ] ∗ ( c n t [ s − t ] i − j ) f[s][i]=\sum_{t\subsetneq s,j\leq i} g[t][j]*\binom {cnt[s-t]} {i-j} f[s][i]=t⊊s,j≤i∑g[t][j]∗(i−jcnt[s−t])
但是有问题啊,如果集合s被分成了三个及以上的块就可能重复计算方案,这个我们按套路强制包括一个定点即可,也就相当于仅仅站在定点的角度看问题,这样就可以做到不重不漏。
最后统计答案时可以把g数组差分一下再乘权值,也可以用下面这种方法。这是类似于整数期望公式时用到的小trick,因为如果加入第i条边还没联通,就会贡献一个 1 m + 1 \frac 1 {m+1} m+11。
a n s = 1 m + 1 ∑ i = 0 m f [ U ] [ i ] ( c n t [ U ] i ) ans=\frac 1 {m+1}\sum_{i=0}^m \frac {f[U][i]} {\binom {cnt[U]} {i}} ans=m+11i=0∑m(icnt[U])f[U][i]
时间复杂度 O ( m 2 3 n ) O(m^23^n) O(m23n),但其实常数比较小,跑得很快。
神仙boshi给了一种概率密度函数的做法= =
思想和概率计算器大致相似,如果我们希望边权不超过x,那么也就是说边有x的概率存在
那么我们同样可以设f[s]表示联通s的概率,这个同样可以用上面的子集dp再定点算出。但其实这个东西可用多项式来描述,也就是f[s](x)表示最小生成树最大边不超过x的概率,求导就是等于x的概率,再乘以权值即可。
你问为啥求导就是等于x的概率?一个显然的想法就是把它再积分了就是小于等于的函数,其实导数描述的是dx内的斜率,也就相当于是这个dx内增加了多少。
#include
#define rg register
using namespace std;
typedef long long ll;
template <typename Tp> inline int getmin(Tp &x,Tp y){return y<x?x=y,1:0;}
template <typename Tp> inline int getmax(Tp &x,Tp y){return y>x?x=y,1:0;}
template <typename Tp> inline void read(Tp &x)
{
x=0;int f=0;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') f=1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
if(f) x=-x;
}
struct data{int v,nxt;}edge[100];
int n,m,p,lim,head[20],cnt[1050];
ll f[1050][50],c[50][50];
double ans;
inline void insert(int u,int v)
{
edge[++p]=(data){v,head[u]};head[u]=p;
edge[++p]=(data){u,head[v]};head[v]=p;
}
void input()
{
int u,v;
read(n);read(m);lim=1<<n;
for(rg int i=1;i<=m;i++)
{
read(u);read(v);
insert(u,v);
}
for(int i=0;i<50;i++)
{
c[i][0]=1ll;
for(int j=1;j<=i;j++) c[i][j]=c[i-1][j-1]+c[i-1][j];
}
}
void dfs(int s,int c)
{
if(cnt[s]) return ;
int tmp;cnt[s]=c;
for(int i=1;i<=n;i++)
if(~s&(1<<i-1))
{
tmp=0;
for(int j=head[i];j;j=edge[j].nxt)
if(s&(1<<edge[j].v-1))
++tmp;
dfs(s|(1<<i-1),cnt[s]+tmp);
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
input();
dfs(0,0);
for(rg int S=1;S<lim;S+=2)
for(rg int s=(S-1)&S;s;s=(s-1)&S)
if(s&1)
for(int i=0;i<=cnt[S];i++)
for(int j=0;j<=i&&j<=cnt[s];j++)
f[S][i]+=(c[cnt[s]][j]-f[s][j])*c[cnt[S^s]][i-j];
for(rg int i=0;i<=m;i++) ans+=f[lim-1][i]*1.0/c[cnt[lim-1]][i];
ans/=m+1;
printf("%.6lf\n",ans);
return 0;
}