有標號DAG計數 [容斥原理 子集反演 組合數學 fft]

Candy?發表於2017-05-03

有標號DAG計數

題目在COGS上

[HZOI 2015]有標號的DAG計數 I

[HZOI 2015] 有標號的DAG計數 II

[HZOI 2015]有標號的DAG計數 III


I

求n個點的DAG(可以不連通)的個數。\(n \le 5000\)

2013年王迪的論文很詳細了

感覺想法很神,自己怎麼想到啊?

首先要注意到DAG中一類特殊的點:入度為0的點。以這些點來分類統計

先是一種\(O(N^3)\)的dp, \(d(i,j)\) i個點j個入度為0,轉移列舉去掉j個後入度為0點的個數,乘上連邊情況

在弱化條件時特殊化

\(f(n, S)\) n個點,只有S中的點入度為0

\(g(n,S)\) n個點,至少S中的點入度為0
\[ g(n, S) = 2^{\mid s\mid (n-\mid S\mid)} g(n-\mid S\mid, \varnothing) \\ g(n, S) = \sum_{S \subset T} f(n, T)\quad (1) \]
子集反演!
\[ f(n, S) = \sum_{S \subset T} (-1)^{\mid T\mid - \mid S\mid} g(n, S)\quad (2) \]
我們目標是求\(g(n, \varnothing)\),

代入\((1),(2)\),然後使用列舉集合大小,乘上組合數的技巧,\(m = \mid T\mid, k = \mid S\mid\)。還需要用\(\binom{n}{k} \binom{k}{m} = \binom{n}{m} \binom{n-m} {k-m}\)替換,最後得到
\[ g(n, \varnothing) = \sum_{k=1}^n (-1)^{k-1} \binom{n}{k} 2^{k(n-k)} g(n-k, \varnothing) \]
完成!

現在嘗試直接考慮這個式子的意義:
\[ n個點DAG個數= \ge 1 個入度為0 - \ge 2個入度為0 + \ge 3.... \]


II

求n個點的DAG(可以不連通)的個數。\(n \le 10^5\)

當然要用fft啦!分治或者多項式求逆。

\(2^{k(n-k)}\)怎麼辦?

和hdu那道題類似,把\((n-k)^2 = n^2 - 2nk + k^2\)代入

但這樣會出現\(2^{\frac{n}{2}}\), 2的逆元\(\mod P-1\)不存在,所以要求2的二次剩餘\(x^2 \equiv 2 \pmod {P-1}\)


III & IIII

求n個點的DAG(必須連通)的個數。\(n \le 5000, n \le 10^5\)

城市規劃類似的思想...

可以不連通 到 連通


Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 5005, P = 10007;
inline int read() {
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

int n, g[N];
int inv[N], fac[N], facInv[N], mi[P+5];
inline int C(int n, int m) {return fac[n] * facInv[m] %P * facInv[n-m] %P;}
int main() {
    freopen("DAG.in", "r", stdin);
    freopen("DAG.out", "w", stdout);
    //freopen("in", "r", stdin);
    n = read();
    inv[1] = fac[0] = facInv[0] = 1;
    for(int i=1; i<=n; i++) {
        if(i != 1) inv[i] = (P - P/i) * inv[P%i] %P;
        fac[i] = fac[i-1] * i %P;
        facInv[i] = facInv[i-1] * inv[i] %P;
    }
    mi[0] = 1;
    for(int i=1; i<=P; i++) mi[i] = mi[i-1] * 2 %P;
    g[0] = 1;
    for(int i=1; i<=n; i++) 
        for(int k=1; k<=i; k++) g[i] = (g[i] + ((k&1) ? 1 : -1) * C(i, k) %P * mi[k * (i-k) % (P-1)] %P * g[i-k] %P) %P;
    printf("%d\n", (g[n] + P) %P);
}

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = (1<<18) + 5, P = 998244353, qr2 = 116195171;
inline int read() {
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

ll Pow(ll a, int b) {
    ll ans = 1;
    for(; b; b >>= 1, a = a * a %P)
        if(b & 1) ans = ans * a %P;
    return ans;
}

namespace fft {
    int rev[N];
    void dft(int *a, int n, int flag) { 
        int k = 0; while((1<<k) < n) k++;
        for(int i=0; i<n; i++) {
            rev[i] = (rev[i>>1]>>1) | ((i&1)<<(k-1));
            if(i < rev[i]) swap(a[i], a[rev[i]]);
        }
        for(int l=2; l<=n; l<<=1) {
            int m = l>>1;
            ll wn = Pow(3, flag == 1 ? (P-1)/l : P-1-(P-1)/l);
            for(int *p = a; p != a+n; p += l) 
                for(int k=0, w=1; k<m; k++, w = w*wn%P) {
                    int t = (ll) w * p[k+m] %P;
                    p[k+m] = (p[k] - t + P) %P;
                    p[k] = (p[k] + t) %P;
                }
        }
        if(flag == -1) {
            ll inv = Pow(n, P-2);
            for(int i=0; i<n; i++) a[i] = a[i] * inv %P;
        }
    }
    int t[N];
    void inverse(int *a, int *b, int l) {
        if(l == 1) {b[0] = Pow(a[0], P-2); return;}
        inverse(a, b, l>>1);
        int n = l<<1;
        for(int i=0; i<l; i++) t[i] = a[i], t[i+l] = 0; 
        dft(t, n, 1); dft(b, n, 1);
        for(int i=0; i<n; i++) b[i] = (ll) b[i] * (2 - (ll) t[i] * b[i] %P + P) %P;
        dft(b, n, -1); for(int i=l; i<n; i++) b[i] = 0;
    }
}

int n, a[N], b[N], len;
ll inv[N], fac[N], facInv[N];
int main() {
    //freopen("in", "r", stdin);
    freopen("dag_count.in", "r", stdin);
    freopen("dag_count.out", "w", stdout);
    n = read();
    inv[1] = fac[0] = facInv[0] = 1;
    for(int i=1; i<=n; i++) {
        if(i != 1) inv[i] = (P - P/i) * inv[P%i] %P;
        fac[i] = fac[i-1] * i %P;
        facInv[i] = facInv[i-1] * inv[i] %P;
    }
    for(int i=1; i<=n; i++) {
        int t = facInv[i] * Pow(Pow(qr2, (ll) i * i %(P-1)), P-2) %P;
        if(i&1) b[i] = P - t; else b[i] = t;
    } 
    b[0] = (b[0] + 1) %P;
    len = 1; while(len <= n) len <<= 1;
    fft::inverse(b, a, len);
    int ans = (ll) a[n] * fac[n] %P * Pow(qr2, (ll) n * n %(P-1)) %P;
    printf("%d\n", ans);
}

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N = 5005, P = 10007;
inline int read() {
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

int n, g[N], f[N];
int inv[N], fac[N], facInv[N], mi[P+5];
inline int C(int n, int m) {return fac[n] * facInv[m] %P * facInv[n-m] %P;}
int main() {
    freopen("DAGIII.in", "r", stdin);
    freopen("DAGIII.out", "w", stdout);
    //freopen("in", "r", stdin);
    n = read();
    inv[1] = fac[0] = facInv[0] = 1;
    for(int i=1; i<=n; i++) {
        if(i != 1) inv[i] = (P - P/i) * inv[P%i] %P;
        fac[i] = fac[i-1] * i %P;
        facInv[i] = facInv[i-1] * inv[i] %P;
    }
    mi[0] = 1;
    for(int i=1; i<=P; i++) mi[i] = mi[i-1] * 2 %P;
    g[0] = 1;
    for(int i=1; i<=n; i++) 
        for(int k=1; k<=i; k++) 
            g[i] = (g[i] + ((k&1) ? 1 : -1) * C(i, k) %P * mi[k * (i-k) % (P-1)] %P * g[i-k] %P) %P;

    f[0] = 1;
    for(int i=1; i<=n; i++) {
        f[i] = g[i];
        for(int j=1; j<i; j++) f[i] = (f[i] - (ll) C(i-1, j-1) * f[j] %P * g[i-j]) %P;
        if(f[i] < 0) f[i] += P;
    }
    printf("%d\n", (f[n] + P) %P);
}

相關文章