BZOJ 3925 ZJOI2015 地震后的幻想乡

Problem

BZOJ

每条边都有一个[0,1]边权,求最小生成树中最大边的期望大小

n个[0,1]的随机变量,第k小的期望是 k n + 1 \frac k {n+1} n+1k

Solution

姑且不论为啥第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]=ts,jig[t][j](ijcnt[st])

但是有问题啊,如果集合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=0m(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内增加了多少。

Code

#include <cstdio>
#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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值