【洛谷3343_BZOJ3925】[ZJOI2015]地震后的幻想乡(状压 DP_期望)

题目:

洛谷 3343

BZOJ 3925

分析:

谁给我说这是个期望概率神题的,明明没太大关系好吧

「提示」里那个结论哪天想起来再问 Jumpmelon 怎么证。

首先,由于开始修路前 \(e_i\) 就已知了,所以显然是按照 \(e_i\) 从小到大的顺序修,直到连通。代价就是最后加入的边的权值。

这个提示非常地良心,同时结合期望的线性性可以发现答案就是对于所有的 \(k(0\leq k\leq m)\) ,任选 \(k\) 条边 恰好 连通 \(n\) 个点的概率乘上第 \(k\) 大的边权值的期望(即提示中的 \(\frac{k}{m+1}\) )。(注意,这里不需要考虑边的顺序。因为相当于每加入一条边都看一眼有没有连通,连通就退出,否则继续。所以当前是否退出只与之前选了哪些边有关,而与具体的顺序无关。)任意选 \(k\) 条边的方案数是 \(C_m^k\) ,恰好联通的概率就是恰好连通的方案数除以 \(C_m^k\) ,我们现在只需要求出恰好联通的方案数即可。于是现在变成了一个 在 CTS2019 中喜闻乐见的 计数题,和期望概率那一套已经没有任何关系了。

\(n\) 很小,考虑状压 DP 。设 \(f_{S,i}\) 表示选了 \(i\) 条边使 \(S\) 集合中的点 恰好 连通的方案数。这个「恰好」不好做, 正难则反 (这步真没想到),设 \(g_{S,i}\) 表示选了 \(i\) 条边还没有连通的方案数,则 \(f_{S,i}=g_{S,i-1}-g_{S,i}\) 。对于 \(g_{S,i}\) 的转移,为了不重不漏地计数,大力枚举某个 特定的点 所在连通块的大小(类似于 【洛谷4841】城市规划(多项式) 的思路),然后这个连通块之外的边可以随便选。即:

\[g_{S,i}=\sum_{T}\sum_{j=0}^i (C_{\mathrm{num}_T}^j-g_{T,j})\cdot C_{\mathrm{num}_{S-T}}^{i-j}\]

其中 \((C_{\mathrm{num}_T}^j-g_{T,j})\) 就是在 \(T\) 中选 \(j\) 条边使其连通(不一定恰好)的方案数,\(T\)\(S\) 的真子集且 \(\mathrm{lowbit}(T)=\mathrm{lowbit}(S)\) (把 \(S\) 中编号最小的点作为上述的「特定的点」),\(S-T\) 表示 \(T\) 相对于 \(S\) 的补集,\(\mathrm{num}_S\) 表示两端均在 \(S\) 中的边的数量。

暴力转移即可。时间复杂度 \(O(3^nm)\)

代码:

组合数最大 \(C_{45}^{22}\approx 4\times 10^{12}\) ,所以直接 long long 就可以存下。

小恐龙教的条件编译真好用

#include 
#include 
#include 
#include 
using namespace std;

namespace zyt
{
    template
    inline bool read(T &x)
    {
        char c;
        bool f = false;
        x = 0;
        do
            c = getchar();
        while (c != EOF && c != '-' && !isdigit(c));
        if (c == EOF)
            return false;
        if (c == '-')
            f = true, c = getchar();
        do
            x = x * 10 + c - '0', c = getchar();
        while (isdigit(c));
        if (f)
            x = -x;
        return true;
    }
    template
    inline void write(T x)
    {
        static char buf[20];
        char *pos = buf;
        if (x < 0)
            putchar('-'), x = -x;
        do
            *pos++ = x % 10 + '0';
        while (x /= 10);
        while (pos > buf)
            putchar(*--pos);
    }
    inline void write(const double &x, const int fixed = 9)
    {
        printf("%.*f", fixed, x);
    }
    typedef long long ll;
    const int N = 10, M = N * N / 2, S = (1 << N);
    ll g[S][M], C[M][M];
    int num[S], n, m;
    struct ed
    {
        int u, v;
    }e[M];
    inline bool check(const int a, const int p)
    {
        return a & (1 << p);
    }
    inline int lowbit(const int x)
    {
        return x & (-x);
    }
    int work()
    {
        read(n), read(m);
        for (int i = 0; i < m; i++)
        {
            read(e[i].u), read(e[i].v);
            --e[i].u, --e[i].v;
        }
        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] + C[i - 1][j - 1];
        }
        for (int i = 0; i < (1 << n); i++)
            for (int j = 0; j < m; j++)
                if (check(i, e[j].u) && check(i, e[j].v))
                    ++num[i];
        for (int i = 0; i < n; i++)
            g[1 << i][0] = 0;
        for (int S = 1; S < (1 << n); S++)
            for (int T = (S - 1) & S; T; T = (T - 1) & S)
                if (lowbit(T) == lowbit(S))
                    for (int i = 0; i <= m; i++)
                        for (int j = 0; j <= i; j++)
                            g[S][i] += (C[num[T]][j] - g[T][j]) * C[num[S ^ T]][i - j];
        double ans = 0;
        for (int i = 1; i <= m; i++)
            ans += double(i) / (m + 1) * 
                (double(g[(1 << n) - 1][i - 1]) / C[m][i - 1] - double(g[(1 << n) - 1][i]) / C[m][i]);
        write(ans, 6);
        return 0;
    }
}
int main()
{
#ifdef BlueSpirit
    freopen("3343.in", "r", stdin);
#endif
    return zyt::work();
}

转载于:https://www.cnblogs.com/zyt1253679098/p/10913026.html

你可能感兴趣的:(【洛谷3343_BZOJ3925】[ZJOI2015]地震后的幻想乡(状压 DP_期望))