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;
}