Luogu P3343 [ZJOI2015]地震后的幻想乡
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Luogu P3343 [ZJOI2015]地震后的幻想乡相关的知识,希望对你有一定的参考价值。
首先转化一下答案:
根据提示,发现其实只需要求出 \\(e_i\\) 对应的排名 \\(rk_i\\) 就可以得出其期望值 \\(\\fracrk_im + 1\\)
所以只需要求排名的期望,最后答案除上 \\(m + 1\\) 就行了
不难想到能把期望值拆成 \\(\\sum_k = 1 ^ m P(k)\\times k\\),可以把期望值拆成对应的概率乘上其权值
发现权值很好求,“加上第 \\(k\\) 条边使原先不连通的图连通”其权值就为 \\(k\\)
那只需考虑求“加上第 \\(k\\) 条边后使原先不连通的图连通”的概率 \\(P(k)\\) 就可算出答案了
感觉 \\(P(k)\\) 不好算,因为需要满足一个条件“当有 \\(k - 1\\) 条边时图不连通”
就考虑对 \\(P(k)\\) 做个前缀和操作 \\(P\'(k)\\sum_i = 0^k P(i)\\),这样 \\(P\'(k) - P\'(k - 1)\\) 就可得到 \\(P(k)\\)
而且发现 \\(P\'(k)\\) 的定义为“用 \\(k\\) 条边使图连通的概率”,没有了“当有 \\(k - 1\\) 条边时图不连通”这个条件,那就感觉好做多了
又根据古典概率的求法,只需要算出“用 \\(k\\) 条边使图连通的方案数”和“选 \\(k\\) 条边的方案数”就可以算出对应的 \\(P\'(k)\\)
发现“选 \\(k\\) 条边方案数”好求,即为 \\(C_m^k\\)
所以只需要算出“用 \\(k\\) 条边使图连通的方案数”\\(f_k\\) 就行了
图连通,便联想到了并查集
这其实就给了一些解题的提示,这道题也可以类似的通过合并两个小集合得到大集合最终算出 \\(f_k\\)
具体来说,可以对每个集合 \\(s\\) 算出“用集合内部的边中的 \\(k\\) 条使 \\(s\\) 里的点组成的图连通”的方案数 \\(f_k, s\\)
发现好像也有点难做,考虑正难则反,设 \\(g_k, s\\) 为“用集合内部的边中的 \\(k\\) 条使 \\(s\\) 里的点组成的图不连通”的方案数,似乎好求些了
当然由于答案需要 \\(f_k, s\\),要从 \\(g_k, s\\) 推出 \\(f_k, s\\)
发现对于 \\(s\\) 里的点组成的图,要么连通,要么不连通,即任意一种情况只属于 \\(f_k, s\\) 或 \\(g_k, s\\)
且能发现设“集合内部的边”的数量为 \\(szl_s\\),则“用集合内部的边中的 \\(k\\) 条”的方案数就是 \\(C_szl_s^k\\)
所以能得到 \\(f_k, s = C_szl_s^k - g_k, s\\)
考虑求 \\(g_k, s\\)
比较容易想到枚举子集把 \\(s\\) 分成两个集合 \\(t, s-t\\),其”内部可以任意连”,但是“两个集合之间不能有连边“
画几个图会发现这样子会算重,原因出自于“内部可以任意连”,举个例子也可以不连,所以就有了很多完全没有连边的情况
对于这个情况,可以直接定义集合 \\(t\\) 满足“集合内的点组成的图连通”,而 \\(s - t\\) 这个集合”随意连边“
感性证明一下:假设现在有 \\(t, t\'\\quad (t\\not = t\')\\)
根据前面的方法,考虑 \\(t\\) 对于 \\(t\'\\) 多出来的一部分点(反过来一样)对于 \\(t\\) 一定与 \\(t\\) 里的点有连边,而对于 \\(t\'\\) 这部分点在 \\(s - \'t\\) 部分,一定与 \\(t\'\\) 里的点没有连边,所以这两个集合绝对不重
其实能发现这个转移还是有重复,因为 \\(t\\) 是 \\(s\\) 子集,则 \\(s - t\\) 肯定也是 \\(s\\) 子集,两部分都连通的情况算重了
考虑选一个点 \\(p\\in s\\),要求 \\(p\\in t\\) 时才能转移,因为这样子就能满足 \\(p\\notin s - t\\),两部分就不会算重了
然后就可以考虑转移了,对于 \\(t\\) 这个集合,“集合内的点组成的图连通”,显然为 \\(f_i, t\\),对于 \\(t - s\\) 这个集合,“任意连边”,因为 \\(t\\) 已经连了 \\(i\\) 条边,所以 \\(s-t\\) 只能连 \\(k - i\\) 条边,明显是一个组合数 \\(C_szl_s-t^k-i\\),因为“两个集合之间不能有连边”,直接乘法原理把两部分乘起来就行了:
\\(g_k, s = \\sum\\limits_t\\in s\\sum\\limits_i = 0^k f_i, t\\times C_szls-t^k - i\\quad (p\\in t)\\)
根据这个式子其实也能发现一个有趣的东西,就是 \\(\\textdp\\) 的顺序是不影响答案的,先枚举 \\(k\\) 和先枚举 \\(s\\) 都可以
初始化就比较简单了,若只有一个点,则没有边也是连通的,若有多个点,则没有边一定不连通
\\(f_0, s = \\begincases1& (s = 2^j)\\\\ 0& (s\\not = 2^j)\\endcases, g_0, s = \\begincases0& (s = 2^j)\\\\ 1& (s\\not = 2^j)\\endcases\\)
时间复杂度 \\(O(3^n m^2)\\)
#include<bits/stdc++.h>
using namespace std;
#define int64 long long
#define double64 long double
const int N = 10, M = 45 + 5;
int E[N][N];
int szl[1 << N];
int64 C[M][M];
int64 f[M][1 << N], g[M][1 << N];
double64 Ph[M], P[M];
int main()
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
int x, y;
scanf("%d%d", &x, &y);
x--, y--, E[x][y] = E[y][x] = 1;
int lim = (1 << n) - 1;
for (int s = 1; s <= lim; s++)
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
if (((s >> i) & 1) && ((s >> j) & 1) && E[i][j])
szl[s]++;
// 预处理集合内部边的个数
for (int i = 0; i <= m; i++)
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
// 预处理组合数
for (int s = 1; s <= lim; s++)
f[0][s] = 0, g[0][s] = 1;
for (int i = 0; i < n; i++)
f[0][1 << i] = 1, g[0][1 << i] = 0;
// 分只有 1 个点或多个点两种情况预处理
for (int s = 1; s <= lim; s++)
for (int k = 1; k <= m; k++)
int mustp;
for (mustp = 0; ! ((s >> mustp) & 1); mustp++);
// 求出“必须包含的”p
for (int t = (s - 1) & s; t; t = (t - 1) & s)
if ((t >> mustp) & 1)
// 必须要包含 p
for (int i = 0; i <= k; i++)
g[k][s] += f[i][t] * C[szl[s ^ t]][k - i];
// 转移
f[k][s] = C[szl[s]][k] - g[k][s];
// 推出 f 的值
double64 ans = 0.0;
for (int i = 0; i <= m; i++)
Ph[i] = 1.0 * f[i][lim] / C[m][i];
// 整个图其实就是 lim(2 ^ n - 1)
for (int i = 1; i <= m; i++)
P[i] = Ph[i] - Ph[i - 1];
// 由概率前缀和推回初始概率
for (int i = 1; i <= m; i++)
ans += 1.0 * P[i] * i;
// 概率 * 权值
printf("%.6Lf", 1.0 * ans / (m + 1));
// 注意求出来的是排名的期望即 rk 的期望,求出 e 的期望需 / (m + 1)
return 0;
「ZJOI2015」地震后的幻想乡
题目
点这里看题目。
分析
首先,设原图的最小生成树的边集为 \\(T\\),则容易得到:
而可以发现 \\(P(t<\\max_x\\in Te_x)\\) 恰好为加入了 \\(\\le t\\) 的边之后,图仍然不连通的概率。因此我们可以直接枚举一个边集 \\(E\'\\subseteq E\\),使得加入了 \\(E\'\\) 后图不连通,期望则可以被表述为:
所以,我们尝试计算出 \\(\\sum_E\'\\subseteq E[|E\'|=k]\\),整个问题也便迎刃而解了。注意 \\(E\'\\) 还需要保证不连通,这其实颇有一点麻烦——我们反之保证连通。设 \\(f_S,k\\) 表示在 \\(S\\subseteq V\\) 的导出子图中,能够保证 \\(S\\) 内点连通且大小恰好为 \\(k\\) 的边集的数量。设 \\(S\\) 的导出子图的边集为 \\(E(S)\\)。正难则反,我们可以想到容斥不连通的情况:
如果直接暴力 DP 是 \\(O(3^nm^2)\\) 的。不过注意到,第二维可以看作是生成函数的指数,我们相当于在进行生成函数运算。而我们又知道,最终 \\(f_V\\) 的生成函数的指数一定是 \\(\\le m\\) 的,因此我们可以预先插入 \\(m+1\\) 个点值,最后再做拉格朗日插值得到每一项系数。
最终我们得到了一个 \\(O(3^nm+m^2)\\) 的算法。
实现还需要一点技巧:
为了方便,由于 \\(\\binom4522\\approx4.1\\times 10^12\\) 并不是很大,long long
也还装得下,我们可以选一个略大于 \\(\\binom4522\\) 的质数,在它的模域下做运算,这样就简单了许多。
最后,由于我们需要输出浮点数结果,运算精度也是我们需要考虑的问题。注意到,答案最终一定是在 \\((0,1)\\) 范围内,因此我们可以输出它 \\(\\bmod 1\\) 之后的结果。这样的话,像 \\(\\binomm-kj\\frac1j+k+1\\bmod 1\\) 这样的运算,就可以转化为 \\(\\frac1j+k+1\\left(\\binomm-kj\\bmod (j+k+1)\\right)\\)。我们可以直接将分子对分母取模,最后算的时候再计算 \\(ans \\bmod 1\\) 即可。
如果不是不调整精度过不了完全图,我会卡吗我?
小结:
-
仍然需要注意常见的运算技巧。这里主要的处理期望的技巧,还是把贡献滚到前缀上,从而转化为概率。
-
注意一下图上的子集 DP 的方法。一般来说,我们会倾向于通过容斥计算,在无向图的连通性和有向图的强连通中都可以这么做;另一方面,强连通图中也有用耳分解的算法。
-
注意实现中的小技巧。尤其是处理精度这一块的,平时接触不多更要注意。
一般来说,我们会把浮点数压缩到一个较小的范围内,从而保证精度。这里进行 \\(\\bmod 1\\) 及后续运算就是为了达成这一点。
你也可以说,我是发现所有整数部分都被抵消了才想到了这个方法
代码
#include <cmath>
#include <cstdio>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
typedef long long LL;
const LL mod = 4116715363889;
const int MAXN = 105, MAXS = ( 1 << 10 ) + 5;
template<typename _T>
void read( _T &x )
x = 0; char s = getchar(); bool f = false;
while( s < \'0\' || \'9\' < s ) f = s == \'-\', s = getchar();
while( \'0\' <= s && s <= \'9\' ) x = ( x << 3 ) + ( x << 1 ) + ( s - \'0\' ), s = getchar();
if( f ) x = -x;
template<typename _T>
void write( _T x )
if( x < 0 ) putchar( \'-\' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + \'0\' );
int fin[MAXN];
LL f[MAXS], g[MAXS];
int cnt[MAXS];
LL C[MAXN][MAXN];
LL ways[MAXN], coe[MAXN];
int N, M;
inline LL Mul( const LL a, const LL b )
return ( a * b - ( LL ) ( ( long double ) a / mod * b ) * mod + mod ) % mod;
inline LL Qkpow( LL, LL );
inline LL Inv( const LL a ) return Qkpow( a, mod - 2 );
inline LL Sub( LL x, const LL v ) return ( x -= v ) < 0 ? x + mod : x;
inline LL Add( LL x, const LL v ) return ( x += v ) >= mod ? x - mod : x;
inline LL Qkpow( LL base, LL indx )
LL ret = 1;
while( indx )
if( indx & 1 ) ret = Mul( ret, base );
base = Mul( base, base ), indx >>= 1;
return ret;
LL Query( const int x )
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
f[S] = 0;
per( i, M, 0 ) f[S] = Add( Mul( f[S], x ), C[cnt[S]][i] );
g[S] = f[S];
for( int T = ( S - 1 ) & S ; T ; T = ( T - 1 ) & S )
if( ( T & ( - T ) ) == ( S & ( - S ) ) )
g[S] = Sub( g[S], Mul( g[T], f[S ^ T] ) );
return g[( 1 << N ) - 1];
inline long double Mod1( const long double x )
return x - floorl( x );
int main()
read( N ), read( M );
rep( i, 1, M )
int u, v; read( u ), read( v );
cnt[( 1 << ( u - 1 ) ) | ( 1 << ( v - 1 ) )] ++;
rep( i, 0, M )
C[i][0] = C[i][i] = 1;
rep( j, 1, i - 1 ) C[i][j] = Add( C[i - 1][j], C[i - 1][j - 1] );
for( int i = 0 ; i < N ; i ++ )
for( int S = 0 ; S < ( 1 << N ) ; S ++ )
if( ! ( S >> i & 1 ) ) cnt[S | ( 1 << i )] += cnt[S];
coe[0] = 1;
rep( i, 1, M + 1 )
per( j, M + 1, 0 )
coe[j] = Mul( coe[j], mod - i );
if( j ) coe[j] = Add( coe[j], coe[j - 1] );
rep( i, 1, M + 1 )
LL inv = Inv( i ), tmp = 1;
rep( j, 0, M + 1 )
if( j ) coe[j] = Sub( coe[j], coe[j - 1] );
coe[j] = Mul( coe[j], mod - inv );
rep( j, 1, M + 1 ) if( i ^ j )
tmp = Mul( tmp, Sub( i, j ) );
tmp = Mul( Query( i ), Inv( tmp ) );
rep( j, 0, M ) ways[j] = Add( ways[j], Mul( tmp, coe[j] ) );
per( j, M + 1, 0 )
coe[j] = Mul( coe[j], mod - i );
if( j ) coe[j] = Add( coe[j], coe[j - 1] );
long double ans = 0;
rep( i, 0, M )
rep( j, 0, M - i )
int cur = i + j + 1;
int res = 1ll * ( C[M - i][j] % cur ) * ( Sub( C[M][i], ways[i] ) % cur ) % cur;
fin[cur] = j & 1 ? ( ( fin[cur] - res ) % cur + cur ) % cur : ( fin[cur] + res ) % cur;
rep( i, 1, M + 1 ) ans = Mod1( ans + ( long double ) fin[i] / i );
printf( "%.6Lf\\n", ans );
return 0;
以上是关于Luogu P3343 [ZJOI2015]地震后的幻想乡的主要内容,如果未能解决你的问题,请参考以下文章