OCPC2023 I. DAG Generation

题目传送门

题意

给你一种DAG生成方式,问生成两张DAG相同的概率是多少。

生成方式为,一开始有\(A,B\)两个集合,A为空集,B中有\(1-n\)每个节点,每次从B中随机取出一个点,然后在A中随机取出一个子集,把子集中的每个点往B中取出的点连一条有向边,然后把取出点放入A。

题解

我们不妨认为第一次生成取出点的顺序就是1,2,3.......n,第二次任意顺序取出点,题目转化为这两次生成相同的概率。显然取出点的顺序不同还是有可能生成同一张图。

对于第\(i\)个取出的点,它可以对前\(i-1\)个点选或者不选连边,一共有\(2^{i-1}\)种不同的选择,所以对于图1,一共有\(2^0*2^1...+2^{n-1}\)种不同的生成方式,每一种生成方式出现的概率是完全相同的,图2在图1的基础上,取出点顺序是不确定, 每一种取点顺序都有那么多生成方式,总的生成方式数就是\(2^0*2^1...+2^{n-1}*n!\), 两个图一起生成,就有\((2^0*2^1...+2^{n-1})^2*n!\)种不同的生成方式.

所以答案就是两张图一起生成时的

\[\frac{两张图相同的生成方式数}{总生成方式数} = \frac{两张图相同的生成方式数}{(2^0*2^1...+2^{n-1})^2*n!} \]

因为每一种不同的生成方式等概率出现.

分母已经知道,关键时分子怎么求, 自然想到我们可以对于图1的每一种生成方式,算出图2种有多少生成方式可以生成同样的图,然后求和就好了。但是这个东西并不好求,由于图2的一种生成方式最多对应图1的一种方式,因为图1不同方式生成的图一定不同, 所以我们考虑反过来计算图2 种有多少生成方式可以在图1中找到对应。

于是问题就转化成了,图2有多少生成方式可以生成一张DAG,使得DAG中的每一个点只存在由编号比它小的点到达的有向边。

考虑这个东西可以dp,\(F_i\)表示\(i\)个点有多少生成方式,然后考虑插入\(i+1\)号点转移,我们先枚举插入的位置,前面已经有\(i\)个数,一共有\(i+1\)种不同的插入位置,不同的插入位置对应图2生成时不同的取点顺序,然后再乘上插入的这个点的决策数,由于插入的这个点比之前所有点都大,所以可以选择任意的比它先取出的点,如果这个点插入j位置,那么就说明前\(j-1\)个点都可以选,一共\(2^{j-1}\)方案。所以对于任意一个\(i\)个点的生成方案,插入\(i+1\)号点后一共会有$(20+21....+2^i)种不同方案,有:

\[F_i = (2^0 + 2^1 + 2^2....2^{i-1})F_{i-1} \]

而这就是分子。


实现

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <queue>
#include <cstdlib>
#define ll long long
using namespace std;

int read(){
    int num=0, flag=1; char c=getchar();
    while(!isdigit(c) && c!='-') c=getchar();
    if(c == '-') flag=-1, c=getchar();
    while(isdigit(c)) num=num*10+c-'0', c=getchar();
    return num*flag;
}

const ll mod = 1e9+9;
const int N = 1e5+1000;

ll ksm(ll x, ll k){
    if(k == 0) return 1;
    if(k == 1) return x;
    ll res = ksm(x, k/2); res=res*res%mod;
    if(k%2) res = res*x%mod;
    return res;
}

ll add2[N], mul2[N], fac[N];
ll F[N];

int main(){
    fac[0]=1; for(int i=1; i<N; i++) fac[i] = fac[i-1]*i%mod;
    add2[0]=1; mul2[0]=1;
    for(int i=1; i<N; i++) add2[i] = (add2[i-1] + ksm(2, i))%mod;
    for(int i=1; i<N; i++) mul2[i] = (mul2[i-1] * ksm(2, i))%mod;

    F[1] = 1;
    for(int i=2; i<N; i++) F[i] = F[i-1]*add2[i-1]%mod;

    int T =  read();
    while(T--){
        ll n=read();
        ll ans = F[n] * ksm(mul2[n-1]*mul2[n-1]%mod*fac[n]%mod, mod-2) %mod;
        ans = (1-ans)%mod + mod;
        printf("%lld\n", ans%mod);
    }
    
    return 0;
}
posted @ 2024-08-13 15:33  ltdJcoder  阅读(6)  评论(0编辑  收藏  举报