有标号的DAG/强连通图计数

COGS索引

一堆神仙容斥+多项式……


有标号的DAG计数 I

考虑\(O(n^2)\)做法:设\(f_i\)表示总共有\(i\)个点的DAG数量,转移考虑枚举DAG上所有出度为\(0\)的点,剩下的点可以选择连向它,剩下的点之间也可以连边。

但是注意到这样子转移可能会存在剩下的点中有点没有出度的情况,考虑容斥解决:设枚举的出度为\(0\)的点的个数为\(i\)时的容斥系数为\(f_i\),那么一个实际上存在\(x\)个出度为\(0\)的点的DAG的贡献就是\(\sum\limits_{i=1}^x \binom{x}{i} f_i = 1\),不难由二项式定理知道\(f_i = (-1)^{i-1}\)

那么转移式就是\(f_i = \sum\limits_{j=1}^i \binom{i}{j} (-1)^{j-1} 2^{j(i-j)} f_{i-j}\)。

#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;

const int MOD = 10007;
int dp[5003] , C[5003][5003] , poww2[6250003] , N;

int main(){
    freopen("DAG.in","r",stdin);
    freopen("DAG.out","w",stdout);
    cin >> N;
    poww2[0] = 1;
    for(int i = 1 ; i <= (N + 1) / 2 * ((N + 1) / 2) ; ++i)
        poww2[i] = (poww2[i - 1] << 1) % MOD;
    for(int i = 0 ; i <= N ; ++i){
        C[i][0] = 1;
        for(int j = 1 ; j <= i ; ++j)
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
    }
    dp[0] = 1;
    for(int i = 1 ; i <= N ; ++i)
        for(int j = 1 ; j <= i ; ++j)
            dp[i] = (dp[i] + ((j - 1) & 1 ? -1 : 1) * dp[i - j] * C[i][j] % MOD * poww2[j * (i - j)] % MOD + MOD) % MOD;
    cout << dp[N];
    return 0;
}

有标号的DAG计数 II

考虑使用多项式优化I中的做法。

一个前置芝士是使用组合数拆\(ij\):\(ij = \binom{i}{2} + \binom{j+1}{2} - \binom{i-j}{2}\)

\[f_i = \sum\limits_{j=1}^i \binom{i}{j} (-1)^{j-1} 2^{j(i-j)} f_{i-j} \]

\[\frac{f_i}{i!} = \sum\limits_{j=1}^i (-1)^{j-1} 2^{\binom{i}{2} - \binom{j}{2} - \binom{i-j}{2}} \frac{f_{i-j}}{j!(i-j)!}\]

\[\frac{f_i}{i!2^\binom{i}{2}} = \sum\limits_{j=1}^i \frac{(-1)^{j-1}}{j!2^\binom{j}{2}} \frac{f_{i-j}}{(i-j)!2^\binom{i-j}{2}}\]

可以直接多项式求逆了。但是值得注意的一件事情是余项:因为\(j\)的下标从\(1\)开始,所以当\(i=0\)的时候,左式求出来为\(1\),但是右式求出来为\(0\)。在多项式运算的时候记得补上这个余项。

#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;

const int _ = (1 << 18) + 7 , MOD = 998244353;

int poww(long long a , long long b){
    int times = 1;
    while(b){
        if(b & 1) times = times * a % MOD;
        a = a * a % MOD; b >>= 1;
    }
    return times;
}

namespace poly{
    const int G = 3 , INV = 332748118;
    int dir[_] , need , invnd , A[_] , B[_];

    void init(int x){
        need = 1;
        while(need < x) need <<= 1;
        invnd = poww(need , MOD - 2);
        for(int i = 1 ; i < need ; ++i)
            dir[i] = (dir[i >> 1] >> 1) | (i & 1 ? need >> 1 : 0);
    }

    void NTT(int *arr , int tp){
        for(int i = 1 ; i < need ; ++i)
            if(i < dir[i])
                arr[i] ^= arr[dir[i]] ^= arr[i] ^= arr[dir[i]];
        for(int i = 1 ; i < need ; i <<= 1){
            int wn = poww(tp == 1 ? G : INV , MOD / i / 2);
            for(int j = 0 ; j < need ; j += i << 1){
                long long w = 1;
                for(int k = 0 ; k < i ; ++k , w = w * wn % MOD){
                    int x = arr[j + k] , y = arr[i + j + k] * w % MOD;
                    arr[j + k] = x + y >= MOD ? x + y - MOD : x + y;
                    arr[i + j + k] = x < y ? x + MOD - y : x - y;
                }
            }
        }
        if(tp != 1)
            for(int i = 0 ; i < need ; ++i)
                arr[i] = 1ll * arr[i] * invnd % MOD;
    }

#define clr(x) memset(x , 0 , sizeof(int) * need)
    void getInv(int *a , int *b , int len){
        if(len == 1){b[0] = poww(a[0] , MOD - 2); return;}
        getInv(a , b , ((len + 1) >> 1));
        memcpy(A , a , sizeof(int) * len); memcpy(B , b , sizeof(int) * len);
        init(len * 2 + 3); NTT(A , 1); NTT(B , 1);
        for(int i = 0 ; i < need ; ++i) A[i] = 1ll * A[i] * B[i] % MOD * B[i] % MOD;
        NTT(A , -1);
        for(int i = 0 ; i < len ; ++i)
            b[i] = (2ll * b[i] - A[i] + MOD) % MOD;
        clr(A); clr(B);
    }
}
using poly::getInv;
#define ch2(x) (1ll * x * (x - 1) / 2)
int F[_] , ans[_] , inv[_] , jc[_] , N;

void init(){
    jc[0] = 1;
    for(int i = 1 ; i <= N ; ++i) jc[i] = 1ll * jc[i - 1] * i % MOD;
    inv[N] = poww(jc[N] , MOD - 2);
    for(int i = N - 1 ; i >= 0 ; --i) inv[i] = inv[i + 1] * (i + 1ll) % MOD;
}

int main(){
    freopen("dag_count.in","r",stdin);
    freopen("dag_count.out","w",stdout);
    cin >> N; init();
    for(int i = 1 ; i <= N ; ++i) F[i] = (MOD + ((i - 1) & 1 ? 1ll : -1ll) * inv[i] * poww(poww(2 , ch2(i)) , MOD - 2) % MOD) % MOD;
    F[0] = 1; getInv(F , ans , N + 1);
    cout << 1ll * ans[N] * jc[N] % MOD * poww(2 , ch2(N)) % MOD;
    return 0;
}

有标号的DAG计数 III

在I和II中我们求出了可以不连通的DAG数量,而在这个部分我们强制要求DAG弱连通。

考虑用总的DAG数量减去不弱联通的DAG数量。设\(g_i\)表示点数为\(i\)的弱联通的DAG数量,总DAG数量就是I中求出的\(f_i\),而对于不连通的DAG,它一定由若干个连通的DAG构成。那么我们考虑枚举\(1\)号点所在的弱联通DAG的大小,可以得到转移式:

\(g_i = f_i - \sum\limits_{j=1}^{i-1} f_jg_{i-j} \binom{i-1}{j-1}\)。直接转移复杂度\(O(n^2)\)。

#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;

const int MOD = 10007;
int dp[5003] , g[5003] , C[5003][5003] , poww2[6250003] , N;

int main(){
    freopen("DAGIII.in","r",stdin);
    freopen("DAGIII.out","w",stdout);
    cin >> N;
    poww2[0] = 1;
    for(int i = 1 ; i <= (N + 1) / 2 * ((N + 1) / 2) ; ++i)
        poww2[i] = (poww2[i - 1] << 1) % MOD;
    for(int i = 0 ; i <= N ; ++i){
        C[i][0] = 1;
        for(int j = 1 ; j <= i ; ++j)
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
    }
    dp[0] = 1;
    for(int i = 1 ; i <= N ; ++i)
        for(int j = 1 ; j <= i ; ++j)
            dp[i] = (dp[i] + ((j - 1) & 1 ? -1 : 1) * dp[i - j] * C[i][j] % MOD * poww2[j * (i - j)] % MOD + MOD) % MOD;
    for(int i = 0 ; i <= N ; ++i){
        g[i] = dp[i];
        for(int j = 1 ; j < i ; ++j)
            g[i] = (g[i] - dp[j] * g[i - j] % MOD * C[i - 1][i - j - 1] % MOD + MOD) % MOD;
    }
    cout << g[N];
    return 0;
}

有标号的DAG计数 IV

有两种做法:

法一

考虑优化III中的递推式。

\[g_i = f_i - \sum\limits_{j=1}^{i-1} f_jg_{i-j} \binom{i-1}{j-1}\]

\[\frac{g_i}{(i-1)!} = \frac{f_i}{(i-1)!} - \sum\limits_{j=1}^{i-1} \frac{f_j}{(j-1)!}\frac{g_{i-j}}{(i-j)!}\]

多项式求逆即可。注意求和的下界和上界是\(1\)和\(i-1\),所以\(g_0\)和\(f_0\)都要等于\(0\)才满足卷积式子。

#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;

const int _ = (1 << 18) + 7 , MOD = 998244353;

int poww(long long a , long long b){
    int times = 1;
    while(b){
        if(b & 1) times = times * a % MOD;
        a = a * a % MOD; b >>= 1;
    }
    return times;
}

namespace poly{
    const int G = 3 , INV = 332748118;
    int dir[_] , need , invnd , A[_] , B[_];

    void init(int x){
        need = 1;
        while(need < x) need <<= 1;
        invnd = poww(need , MOD - 2);
        for(int i = 1 ; i < need ; ++i)
            dir[i] = (dir[i >> 1] >> 1) | (i & 1 ? need >> 1 : 0);
    }

    void NTT(int *arr , int tp){
        for(int i = 1 ; i < need ; ++i)
            if(i < dir[i])
                arr[i] ^= arr[dir[i]] ^= arr[i] ^= arr[dir[i]];
        for(int i = 1 ; i < need ; i <<= 1){
            int wn = poww(tp == 1 ? G : INV , MOD / i / 2);
            for(int j = 0 ; j < need ; j += i << 1){
                long long w = 1;
                for(int k = 0 ; k < i ; ++k , w = w * wn % MOD){
                    int x = arr[j + k] , y = arr[i + j + k] * w % MOD;
                    arr[j + k] = x + y >= MOD ? x + y - MOD : x + y;
                    arr[i + j + k] = x < y ? x + MOD - y : x - y;
                }
            }
        }
        if(tp != 1)
            for(int i = 0 ; i < need ; ++i)
                arr[i] = 1ll * arr[i] * invnd % MOD;
    }

#define clr(x) memset(x , 0 , sizeof(int) * need)
    void getInv(int *a , int *b , int len){
        if(len == 1){b[0] = poww(a[0] , MOD - 2); return;}
        getInv(a , b , ((len + 1) >> 1));
        memcpy(A , a , sizeof(int) * len); memcpy(B , b , sizeof(int) * len);
        init(len * 2 + 3); NTT(A , 1); NTT(B , 1);
        for(int i = 0 ; i < need ; ++i) A[i] = 1ll * A[i] * B[i] % MOD * B[i] % MOD;
        NTT(A , -1);
        for(int i = 0 ; i < len ; ++i)
            b[i] = (2ll * b[i] - A[i] + MOD) % MOD;
        clr(A); clr(B);
    }
}
using poly::getInv;
#define ch2(x) (1ll * x * (x - 1) / 2)
int F[_] , G[_] , ans[_] , tp[_] , inv[_] , jc[_] , N;

void init(){
    jc[0] = 1;
    for(int i = 1 ; i <= N ; ++i) jc[i] = 1ll * jc[i - 1] * i % MOD;
    inv[N] = poww(jc[N] , MOD - 2);
    for(int i = N - 1 ; i >= 0 ; --i) inv[i] = inv[i + 1] * (i + 1ll) % MOD;
}

int main(){
    freopen("dagIV.in","r",stdin);
    freopen("dagIV.out","w",stdout);
    cin >> N; init();
    for(int i = 1 ; i <= N ; ++i) F[i] = (MOD + ((i - 1) & 1 ? 1ll : -1ll) * inv[i] * poww(poww(2 , ch2(i)) , MOD - 2) % MOD) % MOD;
    F[0] = 1; getInv(F , G , N + 1);
    for(int i = 1 ; i <= N ; ++i)
        G[i] = 1ll * G[i] * jc[i] % MOD * poww(2 , ch2(i)) % MOD;
    for(int i = 0 ; i <= N ; ++i)
        tp[i] = 1ll * G[i] * inv[i] % MOD;
    getInv(tp , ans , N + 1); poly::init(2 * N + 2);
    for(int i = 1 ; i <= N ; ++i)
        G[i] = 1ll * G[i] * inv[i - 1] % MOD;
    G[0] = 0;
    poly::NTT(G , 1); poly::NTT(ans , 1);
    for(int i = 0 ; i < poly::need ; ++i)
        ans[i] = 1ll * ans[i] * G[i] % MOD;
    poly::NTT(ans , 0);
    cout << 1ll * ans[N] * jc[N - 1] % MOD;
    return 0;
}

法二

注意到III中的一句话:一个不一定连通的DAG一定由若干个连通的DAG构成,这等价于一个不一定连通的DAG是若干个连通的DAG的有标号集合。计算有标号集合可以考虑多项式Exp,那么如果设连通的DAG的数量的指数型生成函数为\(F\),不一定连通的DAG的数量的指数型生成函数为\(G\),那么\(G = e^F\),即\(F = ln\ G\)。多项式求Ln即可。

#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;

const int _ = (1 << 18) + 7 , MOD = 998244353;

int poww(long long a , long long b){
    int times = 1;
    while(b){
        if(b & 1) times = times * a % MOD;
        a = a * a % MOD; b >>= 1;
    }
    return times;
}

namespace poly{
    const int G = 3 , INV = 332748118;
    int dir[_] , need , invnd , A[_] , B[_] , C[_];

    void init(int x){
        need = 1;
        while(need < x) need <<= 1;
        invnd = poww(need , MOD - 2);
        for(int i = 1 ; i < need ; ++i)
            dir[i] = (dir[i >> 1] >> 1) | (i & 1 ? need >> 1 : 0);
    }

    void NTT(int *arr , int tp){
        for(int i = 1 ; i < need ; ++i)
            if(i < dir[i])
                arr[i] ^= arr[dir[i]] ^= arr[i] ^= arr[dir[i]];
        for(int i = 1 ; i < need ; i <<= 1){
            int wn = poww(tp == 1 ? G : INV , MOD / i / 2);
            for(int j = 0 ; j < need ; j += i << 1){
                long long w = 1;
                for(int k = 0 ; k < i ; ++k , w = w * wn % MOD){
                    int x = arr[j + k] , y = arr[i + j + k] * w % MOD;
                    arr[j + k] = x + y >= MOD ? x + y - MOD : x + y;
                    arr[i + j + k] = x < y ? x + MOD - y : x - y;
                }
            }
        }
        if(tp != 1)
            for(int i = 0 ; i < need ; ++i)
                arr[i] = 1ll * arr[i] * invnd % MOD;
    }

#define clr(x) memset(x , 0 , sizeof(int) * need)
    void getInv(int *a , int *b , int len){
        if(len == 1){b[0] = poww(a[0] , MOD - 2); return;}
        getInv(a , b , ((len + 1) >> 1));
        memcpy(A , a , sizeof(int) * len); memcpy(B , b , sizeof(int) * len);
        init(len * 2 + 3); NTT(A , 1); NTT(B , 1);
        for(int i = 0 ; i < need ; ++i) A[i] = 1ll * A[i] * B[i] % MOD * B[i] % MOD;
        NTT(A , -1);
        for(int i = 0 ; i < len ; ++i)
            b[i] = (2ll * b[i] - A[i] + MOD) % MOD;
        clr(A); clr(B);
    }

    void getDis(int *a , int *b , int len){
        for(int i = 0 ; i < len - 1 ; ++i)
            b[i] = a[i + 1] * (i + 1ll) % MOD;
        b[len - 1] = 0;
    }

    void getInt(int *a , int *b , int len){
        for(int i = 1 ; i <= len ; ++i)
            b[i] = 1ll * a[i - 1] * poww(i , MOD - 2) % MOD;
    }

    void getLn(int *a , int *b , int len){
        getInv(a , C , len); getDis(a , A , len);
        init(2 * len + 1); NTT(A , 1); NTT(C , 1);
        for(int i = 0 ; i < need ; ++i)
            A[i] = 1ll * A[i] * C[i] % MOD;
        NTT(A , -1);
        getInt(A , b , len - 1); clr(A); clr(C);
    }
}
using poly::getInv; using poly::getLn;
#define ch2(x) (1ll * x * (x - 1) / 2)
int F[_] , G[_] , ans[_] , tp[_] , inv[_] , jc[_] , N;

void init(){
    jc[0] = 1;
    for(int i = 1 ; i <= N ; ++i) jc[i] = 1ll * jc[i - 1] * i % MOD;
    inv[N] = poww(jc[N] , MOD - 2);
    for(int i = N - 1 ; i >= 0 ; --i) inv[i] = inv[i + 1] * (i + 1ll) % MOD;
}

int main(){
    freopen("dagIV.in","r",stdin);
    freopen("dagIV.out","w",stdout);
    cin >> N; init();
    for(int i = 1 ; i <= N ; ++i) F[i] = (MOD + ((i - 1) & 1 ? 1ll : -1ll) * inv[i] * poww(poww(2 , ch2(i)) , MOD - 2) % MOD) % MOD;
    F[0] = 1; getInv(F , G , N + 1);
    for(int i = 1 ; i <= N ; ++i)
        G[i] = 1ll * G[i] * poww(2 , ch2(i)) % MOD;
    getLn(G , ans , N + 1);
    cout << 1ll * ans[N] * jc[N] % MOD;
    return 0;
}

有标号的强连通图计数 I

感觉比上面的难不少但是评级却更低是什么鬼

一个任意的有向图在缩点时候都可以得到一个DAG,那么我们可以仍然考虑DAG计数I中的做法,即枚举缩点之后的图中出度为\(0\)的点由哪些点构成。但是值得注意的一件事情是在DAG计数I中,容斥系数与出度为\(0\)的点数相关,那么在强连通图计数中,这个容斥系数应该与选择的点在缩点之后构成的强连通分量的个数相关,相比于DAG计数来说这个是最为棘手的。

考虑设\(f_i\)表示\(i\)个点的强连通图数量,$g_i = \sum\limits_{j=1}^i (-1)^{j-1} \times $\(i\)个点构成\(j\)个强连通分量的方案数,\(h_i = 2^{i(i-1)}\)表示\(i\)个点的有向图数量。不难发现\(g_i\)就是把容斥系数和方案数放在了一起。

那么枚举缩点之后出度为\(0\)的强连通分量由哪些点构成,可以得到转移式:\(f_i = h_i - \sum\limits_{j=1}^i \binom{i}{j} h_{i-j} g_{j} 2^{j(i-j)} + f_i\)。值得注意的是最后加上的\(f_i\),这是因为当\(j=i\)时,\(g_i\)中包含了\(i\)个点构成同一个强连通分量的方案数,所以要把它补回来。

由上面的式子变形一下就可以得到\(g_i\)的转移式:\(g_i = h_i - \sum\limits_{j=1}^{i-1} \binom{i}{j} h_{i-j} g_{j} 2^{j(i-j)}\)。

然后考虑\(f\)和\(g\)的关系。按照上面的定义,\(g_i\)表示的是\(i\)个点构成强连通分量的方案总和,其中奇数个强连通分量的方案贡献为\(1\),偶数个强连通分量的贡献为\(-1\)。那么我们考虑枚举\(1\)号点所在的强连通分量,可以得到转移式:\(g_i = f_i - \sum\limits_{j=1}^{i-1} f_j g_{i-j} \binom{i-1}{j-1}\),即\(f_i = g_i + \sum\limits_{j=1}^{i-1} f_j g_{i-j} \binom{i-1}{j-1}\)。

暴力求出\(f,g\),复杂度\(O(n^2)\)。

#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;

const int _ = 1003 , MOD = 10007;

int poww(long long a , long long b){
    int times = 1;
    while(b){
        if(b & 1) times = times * a % MOD;
        a = a * a % MOD; b >>= 1;
    }
    return times;
}

int F[_] , G[_] , inv[_] , jc[_] , poww2[_ * _] , N;

void init(){
    jc[0] = 1;
    for(int i = 1 ; i <= N ; ++i) jc[i] = jc[i - 1] * i % MOD;
    inv[N] = poww(jc[N] , MOD - 2);
    for(int i = N - 1 ; i >= 0 ; --i) inv[i] = inv[i + 1] * (i + 1) % MOD;
    poww2[0] = 1;
    for(int i = 1 ; i <= N * N ; ++i)
        poww2[i] = poww2[i - 1] * 2 % MOD;
}

int binom(int a , int b){return a < b ? 0 : jc[a] * inv[b] % MOD * inv[a - b] % MOD;}

int main(){
    freopen("QAQ_strong_one.in","r",stdin);
    freopen("QAQ_strong_one.out","w",stdout);
    cin >> N; init();
    for(int i = 1 ; i <= N ; ++i){
        G[i] = poww2[i * (i - 1)];
        for(int j = 1 ; j < i ; ++j)
            G[i] = (G[i] - binom(i , j) * poww2[j * (i - j)] % MOD * poww2[(i - j) * (i - j - 1)] % MOD * G[j] % MOD + MOD) % MOD;
    }
    for(int i = 1 ; i <= N ; ++i){
        F[i] = G[i];
        for(int j = 1 ; j < i ; ++j)
            F[i] = (F[i] + F[j] * G[i - j] % MOD * binom(i - 1 , j - 1)) % MOD;
    }
    cout << F[N];
    return 0;
}

有标号的强连通图计数 II

第一部分:多项式优化求\(g\)。和DAG计数II差不多,推一下式子可以得到\(\frac{g_n}{n!2^\binom{n}{2}} = \frac{h_n}{n!2^\binom{n}{2}} - \sum\limits_{j=1}^{n-1} \frac{h_{n-j}}{(n-j)! 2^\binom{n-j}{2}} \frac{g_j}{j!2^\binom{j}{2}}\),仍然是多项式求逆,仍然需要注意边界。

第二部分:多项式优化求\(f\)。基本思路和DAG计数IV是一样的,有拆式子然后多项式求逆的方法,也可以考虑性质然后多项式Ln解决。

多项式Ln代码

#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;

const int _ = (1 << 18) + 7 , MOD = 998244353;

int poww(long long a , long long b){
    int times = 1;
    while(b){
        if(b & 1) times = times * a % MOD;
        a = a * a % MOD; b >>= 1;
    }
    return times;
}

namespace poly{
    const int G = 3 , INV = 332748118;
    int dir[_] , need , invnd , A[_] , B[_] , C[_];

    void init(int x){
        need = 1;
        while(need < x) need <<= 1;
        invnd = poww(need , MOD - 2);
        for(int i = 1 ; i < need ; ++i)
            dir[i] = (dir[i >> 1] >> 1) | (i & 1 ? need >> 1 : 0);
    }

    void NTT(int *arr , int tp){
        for(int i = 1 ; i < need ; ++i)
            if(i < dir[i])
                arr[i] ^= arr[dir[i]] ^= arr[i] ^= arr[dir[i]];
        for(int i = 1 ; i < need ; i <<= 1){
            int wn = poww(tp == 1 ? G : INV , MOD / i / 2);
            for(int j = 0 ; j < need ; j += i << 1){
                long long w = 1;
                for(int k = 0 ; k < i ; ++k , w = w * wn % MOD){
                    int x = arr[j + k] , y = arr[i + j + k] * w % MOD;
                    arr[j + k] = x + y >= MOD ? x + y - MOD : x + y;
                    arr[i + j + k] = x < y ? x + MOD - y : x - y;
                }
            }
        }
        if(tp != 1)
            for(int i = 0 ; i < need ; ++i)
                arr[i] = 1ll * arr[i] * invnd % MOD;
    }

#define clr(x) memset(x , 0 , sizeof(int) * need)
    void getInv(int *a , int *b , int len){
        if(len == 1){b[0] = poww(a[0] , MOD - 2); return;}
        getInv(a , b , ((len + 1) >> 1));
        memcpy(A , a , sizeof(int) * len); memcpy(B , b , sizeof(int) * len);
        init(len * 2 + 3); NTT(A , 1); NTT(B , 1);
        for(int i = 0 ; i < need ; ++i) A[i] = 1ll * A[i] * B[i] % MOD * B[i] % MOD;
        NTT(A , -1);
        for(int i = 0 ; i < len ; ++i)
            b[i] = (2ll * b[i] - A[i] + MOD) % MOD;
        clr(A); clr(B);
    }

    void getDis(int *a , int *b , int len){
        for(int i = 0 ; i < len - 1 ; ++i)
            b[i] = a[i + 1] * (i + 1ll) % MOD;
        b[len - 1] = 0;
    }

    void getInt(int *a , int *b , int len){
        for(int i = 1 ; i <= len ; ++i)
            b[i] = 1ll * a[i - 1] * poww(i , MOD - 2) % MOD;
    }

    void getLn(int *a , int *b , int len){
        getInv(a , C , len); getDis(a , A , len);
        init(2 * len + 1); NTT(A , 1); NTT(C , 1);
        for(int i = 0 ; i < need ; ++i)
            A[i] = 1ll * A[i] * C[i] % MOD;
        NTT(A , -1);
        getInt(A , b , len - 1); clr(A); clr(C);
    }
}
using poly::getInv; using poly::getLn;
#define ch2(x) (1ll * x * (x - 1) / 2)
int F[_] , W[_] , H[_] , inv[_] , jc[_] , N;

void init(){
    jc[0] = 1;
    for(int i = 1 ; i <= N ; ++i) jc[i] = 1ll * jc[i - 1] * i % MOD;
    inv[N] = poww(jc[N] , MOD - 2);
    for(int i = N - 1 ; i >= 0 ; --i) inv[i] = inv[i + 1] * (i + 1ll) % MOD;
}

int main(){
    freopen("QAQ_strongly_two.in","r",stdin);
    freopen("QAQ_strongly_two.out","w",stdout);
    cin >> N; init();
    for(int i = 1 ; i <= N ; ++i)
        H[i] = 1ll * poww(2 , ch2(i)) * inv[i] % MOD;
    H[0] = 1; getInv(H , W , N + 1); H[0] = 0;
    poly::init(2 * N + 2); poly::NTT(H , 1); poly::NTT(W , 1);
    for(int i = 0 ; i < poly::need ; ++i)
        H[i] = 1ll * H[i] * W[i] % MOD;
    poly::NTT(H , -1); H[0] = 1;
    for(int i = 1 ; i <= N ; ++i)
        H[i] = MOD - 1ll * H[i] * poww(2 , ch2(i)) % MOD;
    getLn(H , F , N + 1);
    cout << MOD - 1ll * F[N] * jc[N] % MOD;
    return 0;
}

多项式求逆代码

#include<bits/stdc++.h>
//this code is written by Itst
using namespace std;

const int _ = (1 << 18) + 7 , MOD = 998244353;

int poww(long long a , long long b){
    int times = 1;
    while(b){
        if(b & 1) times = times * a % MOD;
        a = a * a % MOD; b >>= 1;
    }
    return times;
}

namespace poly{
    const int G = 3 , INV = 332748118;
    int dir[_] , need , invnd , A[_] , B[_] , C[_];

    void init(int x){
        need = 1;
        while(need < x) need <<= 1;
        invnd = poww(need , MOD - 2);
        for(int i = 1 ; i < need ; ++i)
            dir[i] = (dir[i >> 1] >> 1) | (i & 1 ? need >> 1 : 0);
    }

    void NTT(int *arr , int tp){
        for(int i = 1 ; i < need ; ++i)
            if(i < dir[i])
                arr[i] ^= arr[dir[i]] ^= arr[i] ^= arr[dir[i]];
        for(int i = 1 ; i < need ; i <<= 1){
            int wn = poww(tp == 1 ? G : INV , MOD / i / 2);
            for(int j = 0 ; j < need ; j += i << 1){
                long long w = 1;
                for(int k = 0 ; k < i ; ++k , w = w * wn % MOD){
                    int x = arr[j + k] , y = arr[i + j + k] * w % MOD;
                    arr[j + k] = x + y >= MOD ? x + y - MOD : x + y;
                    arr[i + j + k] = x < y ? x + MOD - y : x - y;
                }
            }
        }
        if(tp != 1)
            for(int i = 0 ; i < need ; ++i)
                arr[i] = 1ll * arr[i] * invnd % MOD;
    }

#define clr(x) memset(x , 0 , sizeof(int) * need)
    void getInv(int *a , int *b , int len){
        if(len == 1){b[0] = poww(a[0] , MOD - 2); return;}
        getInv(a , b , ((len + 1) >> 1));
        memcpy(A , a , sizeof(int) * len); memcpy(B , b , sizeof(int) * len);
        init(len * 2 + 3); NTT(A , 1); NTT(B , 1);
        for(int i = 0 ; i < need ; ++i) A[i] = 1ll * A[i] * B[i] % MOD * B[i] % MOD;
        NTT(A , -1);
        for(int i = 0 ; i < len ; ++i)
            b[i] = (2ll * b[i] - A[i] + MOD) % MOD;
        clr(A); clr(B);
    }
}
using poly::getInv;
#define ch2(x) (1ll * x * (x - 1) / 2)
int F[_] , W[_] , H[_] , inv[_] , jc[_] , N;

void init(){
    jc[0] = 1;
    for(int i = 1 ; i <= N ; ++i) jc[i] = 1ll * jc[i - 1] * i % MOD;
    inv[N] = poww(jc[N] , MOD - 2);
    for(int i = N - 1 ; i >= 0 ; --i) inv[i] = inv[i + 1] * (i + 1ll) % MOD;
}

int main(){
    freopen("QAQ_strongly_two.in","r",stdin);
    freopen("QAQ_strongly_two.out","w",stdout);
    cin >> N; init();
    for(int i = 1 ; i <= N ; ++i)
        H[i] = 1ll * poww(2 , ch2(i)) * inv[i] % MOD;
    H[0] = 1; getInv(H , W , N + 1); H[0] = 0;
    poly::init(2 * N + 2); poly::NTT(H , 1); poly::NTT(W , 1);
    for(int i = 0 ; i < poly::need ; ++i)
        H[i] = 1ll * H[i] * W[i] % MOD;
    poly::NTT(H , -1); H[0] = 1;
    for(int i = 1 ; i <= N ; ++i)
        H[i] = MOD - 1ll * H[i] * poww(2 , ch2(i)) % MOD;
    getInv(H , F , N + 1); H[0] = 0;
    for(int i = 1 ; i <= N ; ++i)
        H[i] = 1ll * (MOD - H[i]) * i % MOD;
    poly::init(2 * N + 2); poly::NTT(H , 1); poly::NTT(F , 1);
    for(int i = 0 ; i < poly::need ; ++i)
        H[i] = 1ll * H[i] * F[i] % MOD;
    poly::NTT(H , -1);
    cout << 1ll * H[N] * jc[N - 1] % MOD;
    return 0;
}

原文地址:https://www.cnblogs.com/Itst/p/11072400.html

时间: 2024-10-11 13:34:53

有标号的DAG/强连通图计数的相关文章

有标号的DAG图计数1~4

前言 我什么都不会,菜的被关了起来. 有标号的DAG图I Solution 考虑递推,设\(f_i\)表示i个点的答案,显然这个东西是可以组合数+容斥递推? 设\(f_i\)表示i个点的答案,我们考虑假设现在有j个点入度为1,那么可以选出的点就是一个组合数\(C_i^j\),边的可能性有两种,对应的就是\(2^{j*(i-j)}\),然后接着搞,肯定这样子算会有重复的,所以容斥一下然后和以前的答案乘起来就好了. \(f_i=\sum_{j=1}^{i}f_{i-j}*-1^{j-1}*C_i^j

有标号的DAG计数I~IV

有标号的DAG计数 最近心血来潮来写一写这个玩意儿. 请特别注意定义生成函数时下标的起始位置. 有标号的DAG计数I 求\(n\)点带标号\(DAG\)的数量模\(10007\).\(n\le5000\). 数据范围显然\(O(n^2)\).设\(f_i\)表示答案,枚举\(DAG\)中入度为零的点的数量\(j\),方案数为\(\binom ij\),将图拆成两个部分,前\(j\)个点向后\(i-j\)个点的连边任意,方案数为\(2^{j(i-j)}\),后\(j\)个点构成一张\(DAG\),

有标号的DAG计数 III

Description 给定一正整数n,对n个点有标号的有向无环图进行计数,这里加一个限制:此图必须是弱连通图.输出答案 mod 10007 的结果. Solution 弱连通图即把边变成无向之后成为连通的图 考虑补集转换,用 \(DAG\) 的方案数减去不连通的方案数 设 \(f[i]\) 为大小为 \(i\) 的\(DAG\)的方案数 可以像 \(DAG I\) 那样求出来 \(g[i]\) 为弱连通图的方案数 \(g[n]=f[n]-\sum_{i=1}^{n}g[i]*f[i-j]*C_

[COGS 2353 &amp; 2356] 有标号的DAG计数 容斥原理

COGS 2353 题意 问 n 个点的带标号 DAG 有多少个. n <= 5000 . 分析 DAG 的突破口在于度数为 0 的点, 我们每次将其删去, 则还有一些度数为 0 的点. 设 $f_n$ 为 n 个点的带标号 DAG 个数, 奠基 $f_0 = 1$ , 答案为 $f_n$ . 我们考虑容斥原理, 用至少有 1 个度数为 0 的点的 DAG 个数, 减去至少有 2 个度数为 0 的点的 DAG 个数, 加上至少有 3 个度数为 0 的点的 DAG 个数, ... $$f_n =

有标号的DAG计数系列问题

传送门 II 设 \(f_i\) 表示 \(i\) 个点的答案 那么枚举至少 \(j\) 个点的出度为 \(0\) \[\sum_{j=0}^{i}(-1)^j\binom{i}{j}f_{i-j}2^{(i-j)j}=0\] 所以 \[f_i=\sum_{j=1}^{i}(-1)^{j+1}\binom{i}{j}f_{i-j}2^{(i-j)j}\] 即 \[\frac{f_i}{i!}=\sum_{j=0}^{i-1}\frac{f_j}{j!}\frac{(-1)^{i-j+1}}{(i

cojs 强连通图计数1-2 题解报告

OwO 题目含义都是一样的,只是数据范围扩大了 对于n<=7的问题,我们直接暴力搜索就可以了 对于n<=1000的问题,我们不难联想到<主旋律>这一道题 没错,只需要把方程改一改就可以了 首先我们考虑不合法的方案强连通分量缩点后一定是DAG 考虑子问题:DAG计数 做法可以参考<cojs DAG计数1-4 题解报告> 这里给出转移方程 f(n)=sigma((-1)^(k-1)*C(n,k)*2^(k*(n-k))*f(n-k)) 如果考虑上强连通分量缩点的情况呢? 我

图的计数

无标号计数与带标号计数 无标号就是在观测上不考虑个体的差异, 带标号就是在观测上考虑个体的差异. 对于无标号计数, 我们可以按照某种关键值, 给所有的元素进行定序, 所有无标号计数等价于定序计数. 对于带标号计数, 我们可以任意设置关键值, 所以带标号计数等价于不定序计数. 最常见的例子就是组合与排列, 它们还满足一组特殊的关系: 组合数 * n! = 排列数. 有向图 / 无向图 带标号无向图的 度数的K次方 的和 分析 $ans = n \sum_{d = 0} ^ {n - 1} \bin

【Foreign】树 [prufer编码][DP]

树 Time Limit: 10 Sec  Memory Limit: 256 MB Description Input Output Sample Input 3 2 2 1 Sample Output 3 3 2 HINT Source 由于是带标号的无根树的计数,于是我们运用prufer编码的性质来解题. prufer编码的几个性质: 1.对于大小为s的树,prufer编码是一个长度为 s-2 的序列: 2.i在序列中出现的次数<deg[i]: 3.一个prufer编码表示一棵树. 所以这

opencv-视频处理-实时前景检测-阈值法

阈值法: 对每一帧进行阈值处理,取较低的一个阈值进行二值化处理.假设以下为视频流中的任意一帧 代表任意一点处的亮度值(灰度空间),代表一个固定的阈值,对当前帧做以下二值化处理: 该算法比较适合运动物体的亮度大于周围环境的情况,如夜晚的汽车前灯.尾灯等. 下面基于阈值法的前景检测,完成夜晚视频中车辆的检测.跟踪和计数: [算法的步骤] 1.首先画出感兴趣区域,步骤再此博文已详细描述:视频中画出感兴趣区域 2.对进入感兴趣区域的车辆进行前灯的检测,跟踪和计数 代码如下: #include<iostr