LP3343 [ZJOI2015]地震后的幻想乡 [期望/DP]

先%一发题解里的微积分神仙们

Problem

https://www.luogu.org/problemnew/show/P3343

Solution

为什么写这篇题解呢,因为感觉里面的东西都很典型想总结一下...

子问题1:统计一个联通块有多少种不连通方案 -> 状压dp

子问题2:一个经典的期望问题

从原问题到子问题2再到子问题1是一个很厉害的转化思路呢

因为数据范围很小所以怎么写都行呢


回来写了,因为很重要。

若加入了第$k$条边后图联通,则答案为$\frac{k}{m+1}$。

则若加入第$k$条边后图不联通的概率为$p_k$,则其对答案的贡献是$\frac{1}{m+1}$。

即,$p_k$相当于贡献$\frac{k}{m+1}-\frac{k-1}{m+1}$产生这个事件发生的概率。

这是一个非常经典的期望问题。

因此答案则为$\frac{1}{m+1} \sum \limits_{k=0}^{m} p_k$。现在考虑如何求$p_k$。

令$f_{i,j}$为点集为$i$,选了$j$条边的不联通方案数。 $g_{i,j}$联通方案数。

显然有:$f_{i,j}+g_{i,j}=\binom{cnt_i}{j}$,其中$cnt_i$为状态$i$点集内包含的边数。

转移的话,我们考虑枚举一个定点和其所属联通块$S$。则转移: $$f_{S, i+j}=\sum \limits_{T\subset S} g_{T,i}*\binom{cnt_{S-T}}{j}$$

则$p_k=\frac{f_{all, k}}{C(cnt_{all},k)}$

Code

// Code by ajcxsu
// Problem: after earthquake

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=110, T=1<<11;
ll c[N][N];
ll C(int i, int j) {
    if(i==j || j==0) return 1;
    if(i<j) return 0;
    if(c[i][j]!=-1) return c[i][j];
    return c[i][j]=C(i-1, j-1)+C(i-1, j);
}
char e[N][N];

ll f[T][N], g[T][N];
int cnt[T];

int main() {
    memset(c, -1, sizeof(c));
    ios::sync_with_stdio(false), cin.tie(0);
    int n, m;
    cin>>n>>m;
    int u, v;
    for(int i=0;i<m;i++) cin>>u>>v, u--, v--, e[u][v]=e[v][u]=1;
    int U=1<<n;
    for(int i=0;i<U;i++) {
        for(int u=0;u<n;u++)
        for(int v=u+1;v<n;v++)
            if(((1<<u)&i) && ((1<<v)&i) && e[u][v])
                cnt[i]++;
    }
    for(int i=0;i<U;i++)
        for(int j=0;j<=cnt[i];j++) {
            if(!i) g[i][j]=1;
            else {
                int k=1, S;
                for(;!(k&i); k<<=1);
                S=i^k;
                if(S) {
                    for(int T=(S-1)&S; T; T=(T-1)&S)
                        for(int l=0;l<=j;l++)
                            f[i][j]+=g[T|k][l]*C(cnt[i-T-k], j-l);
                    for(int l=0;l<=j;l++)
                        f[i][j]+=g[k][l]*C(cnt[i-k], j-l);
                }
                g[i][j]=C(cnt[i], j)-f[i][j];
            }
        }
    double ans=0;
    for(int i=0;i<=m;i++) ans+=1.0*f[U-1][i]/C(cnt[U-1],i);
    ans/=m+1;
    printf("%.6lf\n", ans);
    return 0;
}

本文链接:https://acxblog.site/archives/sol-luogu-3343.html
本文采用 CC BY-NC-SA 3.0 协议进行许可