Description

傲娇少女幽香是一个很萌很萌的妹子, 而且她非常非常地有爱心, 很喜欢为幻想乡的人们做一些自己力所能及的事情来帮助他们. 这不, 幻想乡突然发生了地震, 所有的道路都崩塌了. 现在的首要任务是尽快让幻想乡的交通体系重新建立起来. 幻想乡一共有 \(n\) 个地方, 那么 最快的方法当然是修复 \(n-1\) 条道路将这 \(n\) 个地方都连接起来. 幻想乡这 \(n\) 个地方本来是连通的, 一共有 \(m\) 条边. 现在这 \(m\) 条边由于地震的关系, 全部都毁坏掉了. 每条边都有一个修复它需要花费的时间, 第 i 条边所需要的时间为 \(e_i\). 地震发生以后, 由于幽香是一位 人生经验丰富, 见得多了的长者, 她根据以前的经验, 知道每次 地震以后, 每个 \(e_i\) 会是一个 \(0\)\(1\) 之间均匀分布的随机实数. 并且所有 \(e_i\) 都是完全独立的. 现在幽香要出发去帮忙修复道路了, 她可以使用一个神奇的大魔法, 能够 选择需要的那 \(n-1\) 条边, 同时开始修复, 那么修复完成的时间就是这 \(n-1\) 条边的 \(e_i\) 的最大值. 当然幽香会先使用一个更加神奇的大魔法来观察出每条边 \(e_i\) 的值, 然后再选择完成时间最小的方案. 幽香在走之前, 她想知道修复完成的时间的期望是多少呢?

Input

第一行两个数 \(n, m\), 表示地方的数量和边的数量. 其中点从 \(1\)\(n\) 标号.

接下来 \(m\) 行, 每行两个数 \(a, b\), 表示点 \(a\) 和点 \(b\) 之间原来有一条边.

这个图不会有重边和自环.

Output

一行输出答案, 四舍五入保留 6 位小数.

Sample Input

5 4
1 2
1 5
4 3
5 3

Sample Output

0.800000

Hint

以下内容与题意无关, 对于解题也不是必要的.

对于 \(n\)\([0, 1]\) 之间的随机变量 \(x_1, x_2, ..., x_n\), 第 \(k\) 小的那个的期望值是 \(\frac{k}{n+1}\).

Sample Explanation

对于第一个样例, 由于只有 4 条边, 幽香显然只能选择这 4 条, 那么答案就是 4 条边的 \(e_i\) 中最大的数的期望, 由提示中的内容, 可知答案为 0. 8.

Data Range

对于所有数据:\(n \leq 10, m \leq \frac{n \cdot (n - 1)}{2}, n, m \geq 1\).

对于 15% 的数据:\(n \leq 3\).

另有 15% 的数据:\(n \leq 10, m = n\).

另有 10% 的数据:\(n \leq 10, m = \frac{n \cdot (n - 1)}{2}\).

另有 20% 的数据:\(n \leq 5\).

另有 20% 的数据:\(n \leq 8\).

Solution

参见网上各大神做法:

首先这里有一种比较好像的做法, 也是 @Burst_Zero 告诉我的做法, 就是说各种期望用微积分推一下然后瞎搞出来就能出结果的做法. 但是我大概试了一下, 发现精度会被卡差不多 \(5 \times 10^{-7}\) 这个位置,long double 都无法胜任...... 所以为了偷懒不写高精我们 就挖出了这样一个好东西:__float128 (可能有的老旧机器不兹磁, 这样的话就只能手写高位高精了 TAT)

下面是转发 @PoPoQQQ 的题解:http://blog.csdn.net/popoqqq/article/details/44858691

首先既然权值在 \([0,1]\) 之间均匀分布那么两条边权值相同的概率为 0 于是我们只考虑所有边边权都不同的情况

如果最小生成树上的最大边为 \(x\), 那么权值小于 \(x\) 的边一定不能将这个图连通, 而权值 \(\leq x\) 的边就可以

因此对于一个 \(x\), 如果我们求出【只有边权小于 \(x\) 的边存在时这个图不连通】的概率, 那么这个概率就是答案 \(\geq x\) 的概率

不妨设这个概率为 \(f(x)\) 那么这个 \(f(x)\) 其实是关于 x 的一个多项式 而答案就是:

\[\int_0^1 f(x) \cdot dx\]

至于为什么答案是 \(f(x)\) 的积分可以感性理解下...... 我数死早......

下面就是如何求 \(f(x)\)

首先【边权小于 \(x\) 的概率】就是 \(x\), 因此这个问题等价于【每条边有 \(x\) 的概率出现时这个图不连通的概率】

然后就是经典做法了......

clj 只说到这...... 然后我扒了半天代码才反应过来怎么做 QAQ 我好弱 QAQ

这个做法和 POJ1737 连通图的做法是类似的

首先我们考虑补集法求这个图连通的概率 然后用 1 减掉就行了

然后我们枚举每一个诱导子图计算这个子图连通的概率

首先我们任选子图中的一点 \(p\), 然后枚举哪些点与 \(p\) 相连

\(p\) 所在联通块联通的概率为 \(q\),\(x\) 所在联通块与其它点之间共有 \(k\) 条边, 那么 这种情况不连通的概率就是 \(q \cdot (1-x)^k\)

从 1 中减掉所有不连通的概率就是这个状态连通的概率了......

以上是大神题解

话说后面一半内容倒是想出来了, 然而前面的数学推理我比较弱, 所以没有想出来 QAQ

另外还有一个神犇似乎没有用上高精直接求出情况数最后再除, 因为我没有这样做 (至少当前 没有这样做), 所以就不把题解粘过来了, 直接上链接:http://blog.csdn.net/skywalkert/article/details/47792065

Source Code


#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <cstdio>
#include <vector>

using namespace std;
typedef long long lli;
// typedef long double llf;
typedef __float128 llf;
const int maxn = 15, maxm = 100, maxn_pw = 1100;

int lowest_digit(int x) { return x & -x; }
int get_digits(int x) { int res = 0;
    for (; x; x -= lowest_digit(x)) res++;
    return res; }

class Polynomial { public:
    vector<lli> vec;
    int size() { return vec.size(); }
    lli& operator [] (int x) { return vec[x]; }
    Polynomial(int sz) { vec = vector<lli>(sz, 0); }
    Polynomial(void) { vec = vector<lli>(1, 0); }
};

Polynomial operator + (Polynomial a, Polynomial b)
{
    Polynomial c(max(a.size(), b.size()));
    for (int i = 0; i < a.size(); i++)
        c[i] += a[i];
    for (int i = 0; i < b.size(); i++)
        c[i] += b[i];
    return c;
}
Polynomial operator - (Polynomial a, Polynomial b)
{
    Polynomial c(max(a.size(), b.size()));
    for (int i = 0; i < a.size(); i++)
        c[i] += a[i];
    for (int i = 0; i < b.size(); i++)
        c[i] -= b[i];
    return c;
}
Polynomial operator * (Polynomial a, Polynomial b)
{
    Polynomial c(a.size() + b.size() - 1);
    for (int i = 0; i < a.size(); i++)
        for (int j = 0; j < b.size(); j++)
            c[i + j] += a[i] * b[j];
    return c;
}
llf integrate(Polynomial a)
{
    llf res = 0.0;
    for (int i = 0; i < a.size(); i++)
        res += (llf)a[i] / (i + 1);
    return res;
}
ostream& operator << (ostream& out, Polynomial a)
{
    for (int i = a.size() - 1; i > 0; i--)
        out << a[i] << "*x^" << i << " + ";
    out << a[0];
    return out;
}

int n, m, n_pow;
int edges[maxn];

Polynomial pow_prec[maxm]; // pow[i] = (1-x)^i
Polynomial dp[maxn_pw];

int main(int argc, char** argv)
{
    scanf("%d%d", &n, &m);
    n_pow = 1 << n;
    // A fancy way of constructing a graph...
    for (int i = 1, a, b; i <= m; i++) {
        scanf("%d%d", &a, &b);
        edges[a] |= 1 << (b - 1);
        edges[b] |= 1 << (a - 1);
    }
    // Preprocessing powers: (1 - x) ^ i
    pow_prec[0] = Polynomial(1);
    pow_prec[1] = Polynomial(2);
    pow_prec[0][0] = 1; // pow[0] = 1
    pow_prec[1][0] = 1; pow_prec[1][1] = -1; // pow[1] = 1-x
    for (int i = 2; i <= m; i++)
        pow_prec[i] = pow_prec[i - 1] * pow_prec[1];
    // Processing graph connectivity, compressed with binary.
    for (int i = 1; i < n_pow; i++) {
        dp[i] = Polynomial(1);
        dp[i][0] = 1;
        int x; // x is the lowest available digit in i, always gurantted an existent x.
        for (x = 1; x <= n; x++)
            if ((i >> (x - 1)) & 1)
                break;
        // Possibly taken digits off...
        for (int j = i ^ (1<<(x-1)); j; (--j) &= i ^ (1<<(x-1))) {
            int tmp = i ^ j, cnt = 0;
            // Iterate all accessible points and check.
            for (int k = 1; k <= n; k++)
                if ((tmp>>(k-1)) & 1)
                    cnt += get_digits(edges[k] & j);
            // "dynamic programming"
            dp[i] = dp[i] - dp[tmp] * pow_prec[cnt];
        }
    }
    // Evaluating integration
    Polynomial res_poly = pow_prec[0] - dp[n_pow - 1];
    // cout << res_poly << endl;
    llf res = integrate(res_poly);
    cout << fixed << setprecision(6) << (double)res << endl;
    return 0;
}