【BZOJ 3925】[Zjoi2015]地震后的幻想乡 期望概率dp+状态压缩+图论知识+组合数学
神™题........
这道题的提示......(用本苣蒻并不会的积分积出来的)并没有 没有什么卵用 ,所以你发现没有那个东西并不会 不影响你做题 ,然后你就可以推断出来你要求的是我们最晚挑到第几大的边会形成最小生成树(可以看成是在Kruskal),然后我就开始YY了(一本正经)。
首先我想到了枚举边的排列这样比较好做应该可以拿到50分,然后,在此基础上我想到了用Prim式构造最小生成树然后以转移的方式求出对应的每个最晚第几的概率,转移致死....后来我去枚举树的形态,发现不会求每种形态所对应的概率......(这个故事告诉我们像期望概率和一些数学等等这些思维量极其大极其抽象而且特别容易会搞迷糊还有概念性的东西一定要想清楚再乱搞,三思啊,你都不知道你离正解差多少步)
然后我去怂题解.....
woc!为啥我没想到状压呀,10的数据范围啊。
首先进行喜闻乐见的概率期望转化,我们利用那个式子可以得出我们要求出“我们最晚挑到第几大的边会形成最小生成树”的期望,再除(m+1),之后我们发现这样还可以转化为∑第i小的边在总情景下出现的概率,也就是∑已经用了i条边不得不再用下一条边的概率,于是也就是∑已经选了i条边然而并没有联通的概率,好了我们得到了我们最终要求的东西了。
然后开始求,我们要求的是面向所有点的,然而我们发现直接求得有一些困难,所以我们就开始递推,我们可以把子集转移的思想运用到图中,就是从诱导子图中转移,(G'=(V', E'),V'被包含于V,E'={(u, v)|u, v属于V',(u, v)属于E},G'为G的诱导子图。注意:对于V',只要在G中有边,那么在G'中同样应该有边。)
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define ft first #define sd second #define mmp(a,b) (std::make_pair(a,b)) typedef std::pair<int,int> pii; typedef long long LL; typedef long double ld; const int F=1050; const int M=50; const int N=12; int n,m,bin[N],full,cnt[F],num[F]; LL f[F][M],g[F][M],c[M][M]; pii e[M]; int main(){ bin[0]=1,c[0][0]=1; for(int i=1;i<=10;++i)bin[i]=bin[i-1]<<1; for(int i=1;i<=45;++i){ c[i][0]=1; for(int j=1;j<=i;++j) c[i][j]=c[i-1][j-1]+c[i-1][j]; } scanf("%d%d",&n,&m),full=(1<<n)-1; for(int i=1;i<=m;++i) scanf("%d%d",&e[i].ft,&e[i].sd); for(int i=0;i<=full;++i){ for(int j=1;j<=m;++j) if((bin[e[j].ft-1]&i)&&(bin[e[j].sd-1]&i)) ++cnt[i]; for(int j=0;j<n;++j) if(i&bin[j]) ++num[i]; } for(int i=1;i<=full;++i){ if((i&1)==0)continue; if(num[i]==1){ g[i][0]=1;continue; } int stand=i&(-i); for(int j=i;j;j=((j-1)&i)) if(j&stand) for(int k=0;k<=cnt[j];++k) for(int l=0;l<=cnt[i^j];++l) f[i][k+l]+=g[j][k]*c[cnt[i^j]][l]; for(int j=0;j<=cnt[i];++j) g[i][j]=c[cnt[i]][j]-f[i][j]; }ld ans=0.; for(int i=0;i<m;++i) ans+=(ld)f[full][i]/(ld)c[m][i]; ans/=(m+1); printf("%lf",(double)ans); return 0; }
苟利国家生死以, 岂因祸福避趋之。