小C的岛屿 题解

IOI2018 集训队作业 by 任轩笛

statement

小C有n个岛屿。她为了管理岛上的人,决定让这些岛屿连通。

每个岛屿i有一个概率值pi,和一个友好列表Ai

小C首先来到了1号岛屿,她会依次执行下面操作:

  1. 设现在在岛屿x,有px的概率产生一条无向边连接两个随机岛屿,这两个岛屿不会相同,也不会已经有边相连。(即在尚不存在的无向边中随机一条加入图中,不会加自环)

  2. 如果此时所有岛屿连通,她就会心满意足地离开。

  3. 否则她会在当前点的友好列表中,等概率选择一个,走到那座岛屿上。她的不满意度会+1,并重复第1步。

求小C的期望不满意度,答案模1000000007。

tutorial

f[x][m] 表示现在在 x 节点, 已经加入了 m 条边, 图还未联通, 不满意度的期望; 显然 f[1][0] 就是答案.

g[m] 表示当前有 m 条边且图没有联通, 并且加入原图中不存在的一条边后联通的概率.

f[x][m]=(1g[m])px|Sx|kSx(f[k][m+1]+1)+1px|Sx|kSx(f[k][m]+1)

发现这个式子可以分层高斯消元, 复杂度 O(N5).

现在看如何求 g[m], 设 An,m 表示 n 个点 m 条边的无向图方案数, Bn,m 表示 n 个点 m 条边联通无向图方案数:

g[m]=i=1n1(n1i1)j=0mBi,jBni,mji(ni)(An,mBn,m)×((n2)m)

显然, An,m=((n2)m). 现在考虑如何求 Bn,m: 这是一个经典问题, 可以做到 O(N4). 但是由于这道题要对所有的 (n,m) 都要求, 所以还是 O(N5) 的. 但是问题不大

考虑将整个图分成若干子集, 使得不同子集间不能有边, 同一子集中不做要求. 设 h(k) 表示将整个图分成 k 个子集的方案数, 那么含有 x 个联通块的方案会被恰好算到 S(x,k) 次, 其中 S 是第二类斯特林数.

考虑到 i=1nS(n,i)(1)i1(i1)!=[n=1]. 那么我们将所有的 h(k) 乘上 (1)k1(k1)! 再求和, 得到的就是只有一个联通块的方案数.

于是考虑设 h[i][j] 表示用了 i 个点, j 条边的方案数. 由于需要凑出 (1)k1(k1)! 的系数, 可以在转移的时候枚举任意一个点的联通块(而不是1号点所在的联通块)进行转移, 这样就可以带上 k! 的系数, 最后再用一次枚举 1 号点联通块的转移即可. 最终求 Bn,m 的时候还要乘上 (jm) 的系数, 因为转移的时候对于每个子集内部是作完全图来考虑的.

代码:

#include <bits/stdc++.h>
#define N 55
#define M 2555
using namespace std;
const int mod = 1e9 + 7;

inline int fsp(int x, int k = mod - 2) {
    int res = 1;
    while (k) {
        if (k & 1) res = 1ll * res * x % mod;
        x = 1ll * x * x % mod, k >>= 1;
    }
    return res;
}

inline void inc(int &x, long long y) {
    x = (x + y) % mod;
}

int n, m, p[N], a[N][N], B[N][M], C[M][M];
int f[N][M], g[M], h[N][M];
vector<int> S[N];

inline int A(int x, int y) {
    return C[C[x][2]][y];
}

void Gauss() {
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n + 1; ++j)
            a[i][j] = (a[i][j] % mod + mod) % mod;
    for (int i = 1; i <= n; ++i) {
        int inv = fsp(a[i][i]);
        for (int j = i + 1; j <= n; ++j) {
            int tmp = 1ll * inv * a[j][i] % mod;
            for (int k = i; k <= n + 1; ++k)
                inc(a[j][k], mod - 1ll * a[i][k] * tmp % mod);
        }
    }
    for (int i = n; i >= 1; --i) {
        for (int j = i + 1; j <= n; ++j)
            inc(a[i][n + 1], mod - 1ll * a[j][j] * a[i][j] % mod);
        a[i][i] = 1ll * a[i][n + 1] * fsp(a[i][i]) % mod;
    }
}

int main() {
    cin >> n, m = n * (n - 1) / 2;
    for (int i = 1; i <= n; ++i)
        scanf("%d", p + i);
    for (int i = 1; i <= n; ++i) {
        int k; scanf("%d", &k);
        S[i].resize(k);
        for (int j = 0; j < k; ++j)
            scanf("%d", &S[i][j]);
    }
    for (int i = 0; i <= m; ++i)
        for (int j = C[i][0] = 1; j <= i; ++j)
            C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
    h[0][0] = 1;
    for (int i = 1; i <= n; ++i)
        for (int j = 0; j <= C[i][2]; ++j)
            for (int k = 1; k <= i && C[k][2] <= j; ++k)
                inc(h[i][j], mod - 1ll * h[i - k][j - C[k][2]] * C[i][k] % mod);
    for (int i = n; i >= 1; --i)
        for (int j = C[i][2]; j >= 0; --j) {
            h[i][j] = 0;
            for (int k = 1; k <= i && C[k][2] <= j; ++k)
                inc(h[i][j], 1ll * h[i - k][j - C[k][2]] * C[i - 1][k - 1]);
        }
    for (int i = 1; i <= n; ++i)
        for (int j = 0; j <= m; ++j)
            for (int k = j; k <= m; ++k)
                inc(B[i][j], 1ll * h[i][k] * C[k][j]);
    for (int i = 0, u, v; i <= m; ++i) {
        u = 0, v = 1ll * (A(n, i) - B[n][i] + mod) % mod * (m - i) % mod;
        for (int x = 1; x < n; ++x)
            for (int e = 0; e <= i; ++e)
                inc(u, 1ll * C[n - 1][x - 1] * B[x][e] % mod * B[n - x][i - e] % mod * x * (n - x));
        g[i] = 1ll * u * fsp(v) % mod;
    }
    for (int j = m; j >= 0; --j) {
        memset(a, 0, sizeof a);
        for (int i = 1; i <= n; ++i) {
            int inv = fsp(S[i].size()), x;
            x = 1ll * (mod + 1 - g[j]) * p[i] % mod * inv % mod;
            a[i][i] = 1;
            a[i][n + 1] = mod + 1 - p[i];
            for (int k : S[i]) {
                a[i][k] = 1ll * (p[i] - 1) * inv % mod;
                inc(a[i][n + 1], 1ll * x * (f[k][j + 1] + 1));
            }
        }

        Gauss();
        for (int k = 1; k <= n; ++k)
            f[k][j] = a[k][k];
    }
    return cout << f[1][0] << endl, 0;
}
posted @   Jerx2y  阅读(258)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示