[ZJOI2015]地震后的幻想乡
VII.[ZJOI2015]地震后的幻想乡
本题有两种思路。
一种思路是从暴力入手并优化状态。
我们考虑边的一组排列。它是将边按照边权从小到大排列的结果。则我们在这组排列上跑Kruskal,设在加入排名为的边时跑出了一棵生成树,则这组排列的答案就是排名为的边权期望,按照题面中的提示,它就是。
枚举每一种排列——它们都是等概率出现的——就能求出总概率。则这个算法的时间复杂度是。
考虑优化。因为我们要求的是生成树中最大边,所以多加入一些边并不会影响最大边的大小,于是我们实际上只要找出一个使得之前图不连通,在加入这条边后图联通了的位置即可。
于是,我们发现在我们Kruskal加入一条新边之时,我们只考虑之前放了多少条边以及图的连通性即可。
发现单独求出加入多少条边时刚好联通不好求;于是我们干脆做个后缀和,设表示加入条边后集合联通的方案数,最后做一个差分即可求出所有刚好联通的方案数。
发现不好求。于是我们采取正难则反,考虑设表示集合不连通的方案数。
我们考虑如何求出。一个显然的想法是枚举子集,将集合切成两半处理。为了不重不漏地计数,我们就强制令一半联通,另一半随便连,同时两半间不连边。这里我们采取计数问题中的经典策略,找出中的lowbit位,设为,并且强制令联通的集合中必须包含(这就相当于枚举所在的连通块)。
我们设表示集合内部共有多少条边。则我们有
其中,枚举子集,枚举中有多少条边,二项式系数的意义是从集合(其中是集合减符号)中选出条边的方案数。
另,我们又有
这很好理解,因为联通数加上不连通数必定等于总方案数。故我们就可以直接通过该式求出。
则我们设表示加入条边时整张图联通的概率。于是有
其中是全体点集。
对做差分就得到位置刚好联通的概率;概率再乘上权值()就得到了期望。
时间复杂度。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,lim;
bool s[20][20];
ll f[100][1<<10],g[100][1<<10],C[100][100],sz[1<<10];
double h[100],res;
int main(){
scanf("%d%d",&n,&m),lim=1<<n;
for(int i=0;i<=m;i++)C[i][0]=1;
for(int i=1;i<=m;i++)for(int j=1;j<=i;j++)C[i][j]=C[i-1][j-1]+C[i-1][j];
for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),x--,y--,s[x][y]=s[y][x]=true;
for(int i=0;i<lim;i++)g[0][i]=1;
for(int i=0;i<n;i++)f[0][1<<i]=1,g[0][1<<i]=0;
for(int i=1;i<lim;i++){
sz[i]=sz[i^(i&-i)];
int p=__builtin_ctz(i);
for(int j=0;j<n;j++)if(i&(1<<j))sz[i]+=s[j][p];
}
for(int i=1;i<=m;i++)for(int j=1;j<lim;j++){
int p=__builtin_ctz(j);
for(int k=(j-1)&j;k;k=(k-1)&j)if(k&(1<<p))for(int l=0;l<=i;l++)g[i][j]+=f[l][k]*C[sz[j^k]][i-l];
f[i][j]=C[sz[j]][i]-g[i][j];
}
for(int i=0;i<=m;i++)h[i]=1.0*f[i][lim-1]/C[m][i];
for(int i=m;i>=1;i--)h[i]-=h[i-1];
for(int i=0;i<=m;i++)res+=h[i]*i;
printf("%lf\n",res/(m+1));
return 0;
}
另一种做法是从积分角度分析。
我们设为最大边为的概率。则我们要求的期望则为
仿照前一种思路,我们设 ,则有
下面我们考虑如何求出上式。观察到的实际意义是最大边大于等于的概率;于是我们设表示集合中最大边大于等于的概率,则就有,其中是全部节点集合。
我们考虑转移出:我们这次考虑枚举所在的那个连通块,设此连通块为,则联通的概率是,不与其它部分联通的概率是,其中是与其补集间边数。
于是就有
考虑到我们最终要求的是;于是开始推式子:
到这里,我们停一下,设一个
则代入上面的式子,就有
于是现在的目标就是求出任意的;这很简单,只需要类似地代入式子中强推即可得到
显然这就可以地DP了。最终答案即为。
代码(极致压行版——它甚至没有我一个推导的式子长):
#include<stdio.h>
int n,m,in[1<<10],lim;
double f[1<<10][100];
int main(){
scanf("%d%d",&n,&m),lim=1<<n;
for(int i=1,x,y;i<=m;i++){scanf("%d%d",&x,&y),x--,y--;for(int j=0;j<lim;j++)if((j&(1<<x))&&(j&(1<<y)))in[j]++;}
for(int i=0;i<lim;i++)if(i&1)for(int j=(i-1)&i;j;j=(j-1)&i)if(j&1)for(int k=0,l=in[i]-in[j]-in[i^j];k+l<=m;k++)f[i][k]+=1.0/(k+l+1)-f[j][k+l];
printf("%.6lf\n",f[lim-1][0]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?