luogu P3343 [ZJOI2015]地震后的幻想乡
link: https://www.luogu.com.cn/problem/P3343
首先orz积分大佬
然而我并不会积分
在努(tou)力(kan)思(ti)考(jie)后终于做了出来
这题就是求最小生成树的最大边的期望🐎
假设边权相对大小已经知道,那么
设最小生成树的最大边的排名为
i
i
i的概率为
P
(
i
)
P(i)
P(i),
很容易得到 a n s = ∑ i = 1 m i m + 1 ∗ P ( i ) ans=\sum\limits_{i=1}^m \frac{i}{m+1}*P(i) ans=i=1∑mm+1i∗P(i)
即 a n s = 1 m + 1 ∑ i = 1 m i ∗ P ( i ) 即ans=\frac{1}{m+1}\sum\limits_{i=1}^m i*P(i) 即ans=m+11i=1∑mi∗P(i)
a n s = 1 m + 1 ∑ i = 1 m ∑ j = 1 i P ( i ) ans=\frac{1}{m+1}\sum\limits_{i=1}^m \sum\limits_{j=1}^iP(i) ans=m+11i=1∑mj=1∑iP(i)
a n s = 1 m + 1 ∑ i = 1 m ∑ j = i m P ( j ) ( 和 上 一 条 是 等 价 的 ) ans=\frac{1}{m+1}\sum\limits_{i=1}^m \sum\limits_{j=i}^mP(j)(和上一条是等价的) ans=m+11i=1∑mj=i∑mP(j)(和上一条是等价的)
考虑
∑
j
=
i
m
P
(
j
)
\sum\limits_{j=i}^mP(j)
j=i∑mP(j)是什么意思,发现就是选了排名前
i
−
1
i-1
i−1条边,图还不连通的概率
这个怎么算?
就是
∑
j
=
i
m
P
(
j
)
=
不
连
通
的
方
案
数
总
的
方
案
数
(
加
入
i
−
1
条
边
后
)
\sum\limits_{j=i}^mP(j)=\frac{不连通的方案数}{总的方案数}(加入i-1条边后)
j=i∑mP(j)=总的方案数不连通的方案数(加入i−1条边后)
方案数的计算可以考虑DP(子集DP)
设 d p [ S ] [ i ] [ 0 / 1 ] 表 示 点 集 为 S , 加 入 了 i 条 边 , 连 通 与 否 设dp[S][i][0/1]表示点集为S,加入了i条边,连通与否 设dp[S][i][0/1]表示点集为S,加入了i条边,连通与否
s i z e [ S ] 表 示 点 集 S 中 的 边 的 数 量 size[S]表示点集S中的边的数量 size[S]表示点集S中的边的数量
易 得 d p [ S ] [ i ] [ 0 ] + d p [ S ] [ i ] [ 1 ] = C s i z e [ S ] i 易得dp[S][i][0]+dp[S][i][1]=C_{size[S]}^i 易得dp[S][i][0]+dp[S][i][1]=Csize[S]i
连通的很容易转移
d p [ S ] [ i ] [ 1 ] = C s i z e [ S ] i − d p [ S ] [ i ] [ 0 ] dp[S][i][1]=C_{size[S]}^i - dp[S][i][0] dp[S][i][1]=Csize[S]i−dp[S][i][0]
对于不连通的我们可以先钦定一个点作为关键点(套路)
然后枚举关键点所在的连通块的点集
然后把这一块孤立
在转移即可
具体还是看代码ba
code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n, m, size[1 << 11];
ll c[55][55], f[1 << 11][55][2];
int main() {
scanf("%d%d", &n, &m);
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] + c[i - 1][j - 1];
for(int i = 1; i <= m; i ++) {
int u, v;
scanf("%d%d", &u, &v);
for(int S = 0; S < (1 << n); S ++)
if(((1 << (u - 1)) & S) && ((1 << (v - 1)) & S)) size[S] ++;
}
for(int S = 0; S < (1 << n); S ++) {
int ha = S & (- S);//ha作为关键点
for(int S0 = S & (S - 1); S0; S0 = (S0 - 1) & S) if(S0 & ha){//S0为关键点的连通块
for(int a = 0; a <= size[S0]; a ++)
for(int b = 0; b <= size[S ^ S0]; b ++)
f[S][a + b][0] += f[S0][a][1] * c[size[S ^ S0]][b]; //把S0这一块孤立,转移非连通的
}
for(int a = 0; a <= size[S]; a ++) f[S][a][1] = c[size[S]][a] - f[S][a][0];//转移连通的
}
long double ans = 0.0;
for(int i = 0; i <= m; i ++) ans += f[(1 << n) - 1][i][0] * 1.0 / (c[m][i] * 1.0);//记得除总方案数
printf("%.6Lf", ans * 1.0 / (m * 1.0 + 1.0));//最后记得除m+1
return 0;
}
代码不长,但是…
感受到了被DP和期望支配的恐惧
😱