2025 CCPC 东北省赛 J 王国——回忆

图计数和树计数就是纯纯科技,指南 有标号的DAG/强连通图计数

考虑 有向图强连通图计数,求出每个强连通分量的大小,其可以直接对答案产生贡献,接下来考虑缩点为普通 DAG,定义冗余边为 \(A \to B\),且 \(A\) 的其他后继点可以到达 \(B\),利用 bitset 维护点 \(i\) 的所有后继可达节点 \(j\) 的集合,于是我们可以 \(\text{or}\)\(A\) 所有后继点的可达集合,枚举每个后继点是否在 \(A\) 的后继点覆盖集合中即可。

对于图计数,等我想写的时候在写吧,不过可以给一个这题的代码看看(由于是用的赛时代码调的,所以写得不好),复杂度 \(O(\frac{n^3}{w})\)

#include "bits/stdc++.h"
using namespace std;
#define int long long
typedef long long ll;
const int N = 2010;
const ll mod = 998244353;
int n, e[N][N];
ll C[N][N];
ll pow2[N * N], f[N], g[N], h[N];
ll ans = 1, dp[N];

ll qpow(ll a, ll k = mod - 2) {
    ll res = 1;
    while (k) {
        if (k & 1) res = res * a % mod;
        k >>= 1;
        a = a * a % mod;
    }
    return res;
}


int dfn[N],low[N],s[N],ins[N],tp;
int scc[N],sc;
int sz[N];
int dfstime;

void tarjan(int u) {
    dfn[u]=low[u]=++dfstime; s[++tp]=u; ins[u]=1;
    for(int i=1;i<=n;++i) {
        if(!e[u][i])  continue;
        if(!dfn[i]) {
            tarjan(i);
            low[u]=min(low[u],low[i]);
        } else if(ins[i]) {
            low[u]=min(low[u],dfn[i]);
        }
    }
    if(dfn[u]==low[u]) {
        ++sc;
        do {
            scc[s[tp]]=sc;
            sz[sc]++;
            ins[s[tp]]=0;
        } while(s[tp--]!=u);
    }
}

int d[N];
int ne[N][N];
vector<int> edge[N];
bitset<2001> bit[N];

void solve() {
    cin >> n;
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= n; j ++ ) {
            cin >> e[i][j];
        }
    }

    // precalc
    pow2[0] = f[0] = g[0] = h[0] = 1;
    for (int i = 0; i <= n; i ++ ) C[i][0] = 1;
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= i; j ++ ) {
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
        }
    }
    for (int i = 1; i < N * N; i ++ ) pow2[i] = pow2[i - 1] * 2 % mod;
    for (int i = 1; i <= n; i ++ ) {
        g[i] = h[i] = pow2[i * (i - 1)];
        for (int j = 1; j <= i - 1; j ++ ) {
            (g[i] -= C[i][j] * pow2[j * (i - j)] % mod * h[i - j] % mod * g[j] % mod) %= mod;
            g[i] = (g[i] + mod) % mod;
        }
        f[i] = g[i];
        for (int j = 1; j <= i - 1; j ++ ) {
            (f[i] += C[i - 1][j - 1] * f[j] % mod * g[i - j] % mod) %= mod;
        }
    }

    // tarjan
    for (int i = 1; i <= n; i ++ ) if (!dfn[i]) tarjan(i);
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= n; j ++ ) {
            if (e[i][j] && scc[i] != scc[j]) ne[scc[j]][scc[i]] = 1;
            if (!e[i][j] && scc[i] == scc[j]) return cout << "0\n", void();
        }
    }
    for (int i = 1; i <= sc; i ++ ) {
        for (int j = 1; j <= sc; j ++ ) {
            if (ne[i][j]) d[j] ++ , edge[j].push_back(i);
        }
    }
    for (int i = 1; i <= sc; i ++ ) ans = ans * f[sz[i]] % mod;
    queue<int> q;
    for (int i = 1; i <= sc; i ++ ) if (!d[i]) q.push(i);
    while (q.size()) {
        int i = q.front(); q.pop();
        for (int j = 1; j <= sc; j ++ ) {
            if (ne[i][j]) {
                d[j] -- ; 
                bit[j] |= bit[i];
                bit[j][i] = 1;
                if (!d[j]) q.push(j);
            }
        }
    }
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= n; j ++ ) {
            if (bit[scc[i]][scc[j]] && !e[i][j]) {
                return cout << "0\n", void();
            }
        }
    }
    for (int i = 1; i <= sc; i ++ ) {
        bitset<2001> reach = 0;
        for (auto j : edge[i]) reach |= bit[j];
        for (auto j : edge[i]) {
            if (reach[j] != 1) ans = ans * (mod + qpow(2, sz[i] * sz[j]) - 1) % mod;
            else ans = ans * qpow(2, sz[i] * sz[j]) % mod;
        }
    }
    cout << ans << "\n";
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int T = 1;
    // cin >> T;
    while (T -- ) solve();

    return 0;
}
posted @ 2025-05-28 22:10  YipChip  阅读(45)  评论(0)    收藏  举报