小清新计数题
挺有意思的计数。
把 \(0\) 看成黑边,\(1\) 看成白边,就是要你求每个环上都有偶数条黑边的基环森林计数。
假设基环树一共 \(i\) 条白边 \(j\) 条黑边,环上面有 \(a\) 条白边,\(b\) 条黑边,就有 \(n = a + b\) 个点。连成环的方案数是 \((a + b - 1)!\)。因为每个点有一条出边,且最后一个点的出边固定为第一个点。
剩下的 \(m = i + j - a - b\) 条边会形成若干棵树,挂在环上。根据 Prufer 序列,一共有 \(n \times (n + m) ^ {m - 1}\) 种方案,具体可以见我的 blog中的第三个应用。
于是考虑 dp,\(f_{i, j}\) 表示 \(i\) 条白边 \(j\) 条黑边连成一颗基环树的方案数,记 \(g_{n, m}\) 表示环上 \(n\) 条边,不在环上有 \(m\) 条边的方案数。显然 \(m = 0\) 时 \(g_{n, m} = (n-1)!\),否则 \(g_{n, m} = (n-1)! \times n \times (n + m) ^ {m - 1} = n! \times (n + m) ^ {m - 1}\)。
\[f_{i, j} = \sum\limits_{a \le i, b \le j, 2 \mid b} \dbinom{i}{a}\dbinom{j}{b}g_{a + b, i + j - a - b}
\]
然后考虑统计答案,\(ans_{i, j}\) 表示 \(i\) 条白边 \(j\) 条黑边连成基环森林的方案数。你发现直接类似背包一样转移会算重,然后发现对于一棵新加的基环树,你只要强制当前没用到的编号最小的点在这棵基环树里即可。给出 \(i > 0\) 时的转移:
\[ans_{i, j} = \sum_{a \le i, b \le j} ans_{i - a, j - b} \dbinom{i - 1}{a - 1} \dbinom{j}{b} f_{a, b}
\]
\(\dbinom{i - 1}{a - 1}\) 是因为强制了最小的白边在这棵树里。
时间复杂度 \(\mathcal{O}(n^4)\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// typedef __int128 i128;
typedef pair<int, int> pii;
const int N = 60, mod = 998244353;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
cout << arg << ' ';
dbg(args...);
}
namespace Loop1st {
int c0, c1;
ll f[N][N], g[N][N], ans[N][N], fac[N], ifac[N];
char s[N];
ll qpow(ll a, ll b) {
ll res = 1;
for (; b; b >>= 1, a = a * a % mod) if (b & 1) res = res * a % mod;
return res;
}
ll C(int n, int m) {
if (n < 0 || m < 0 || n < m) return 0ll;
return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
void main() {
cin >> (s + 1);
int n = (int)strlen(s + 1);
fac[0] = ifac[0] = 1;
for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % mod;
ifac[n] = qpow(fac[n], mod - 2);
for (int i = n - 1; i; i--) ifac[i] = ifac[i + 1] * (i + 1) % mod;
for (int i = 1; i <= n; i++) c0 += (s[i] == '0'), c1 += (s[i] == '1');
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= n - i; j++) {
g[i][j] = fac[i - 1];
if (j) g[i][j] = g[i][j] * i % mod * qpow(i + j, j - 1) % mod;
}
}
for (int i = 0; i <= c1; i++) for (int j = 0; j <= c0; j++) {
for (int a = 0; a <= i; a++) for (int b = 0; b <= j; b += 2) if (a | b) {
f[i][j] = (f[i][j] + C(i, a) * C(j, b) % mod * g[a + b][i + j - a - b] % mod) % mod;
}
}
ans[0][0] = 1;
for (int i = 0; i <= c1; i++) for (int j = 0; j <= c0; j++) if (i | j) {
if (i) {
for (int a = 1; a <= i; a++) for (int b = 0; b <= j; b++) {
ans[i][j] = (ans[i][j] + ans[i - a][j - b] * C(i - 1, a - 1) % mod * C(j, b) % mod * f[a][b] % mod) % mod;
}
} else {
for (int b = 1; b <= j; b++) {
ans[i][j] = (ans[i][j] + ans[i][j - b] * C(j - 1, b - 1) % mod * f[i][b] % mod) % mod;
}
}
}
cout << ans[c1][c0] << '\n';
}
}
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T = 1;
// cin >> T;
while (T--) Loop1st::main();
return 0;
}

浙公网安备 33010602011771号