51nod1835 完全图
不难想到一个状态,令 \(dp_{i, j}\) 为大小为 \(i\) 的完全图划分成 \(j\) 个联通块的方案,下面考虑怎么转移。
不难发现我们可以枚举任意一个点再枚举他所在联通块的大小,这样就能转移了,但这样会记重,因为同一种方案我们会在枚举不同的点时都计算一遍,一个经典的想法就是我们枚举 \(1\) 号点所在联通块的大小,那么就有转移:
\[dp_{i, j} = \sum\limits_{k = 1} ^ {i - 1} dp_{k, 1} \times dp_{i - k, j - 1} \times \dbinom{i - 1}{k - 1}
\]
因为两两之间的连边是不同的,因此和 \(1\) 相联通的点是有区别的,因此我们还需要在剩下的 \(i - 1\) 个点中选出 \(k - 1\) 个点。但于此同时我们会发现上面的那个 \(dp\) 对 \(j = 1\) 无效,因此 \(dp_{i, 1}\) 的情况我们需要提前算出来。
令 \(f_i = dp_{i, 1}\),直接计算不好算可以考虑容斥。同样的,我们枚举 \(1\) 号点所在联通块的大小,将这个连通块和剩下的点之间的边切断,剩下的点之间边随意切或不切,令 \(g_i = 2 ^ {\binom{i}{2}}\),那么有 \(f_i = g_i - \sum\limits_{j = 1} ^ {i - 1} f_j \times g_{i - j} \times \dbinom{i - 1}{j - 1}\)。我们先预处理出 \(f\) 然后就可以直接 \(dp\) 了。注意特判 \(m = 1\) 的情况,可能一条边都没删,因此还需要 \(-1\)。
#include<bits/stdc++.h>
using namespace std;
#define N 500 + 5
#define Mod 998244353
#define rep(i, l, r) for(int i = l; i <= r; ++i)
int n, m, f[N], g[N], fac[N], inv[N], dp[N][N];
int read(){
char c; int x = 0, f = 1;
c = getchar();
while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int Inc(int a, int b){
return (a += b) >= Mod ? a - Mod : a;
}
int Dec(int a, int b){
return (a -= b) < 0 ? a + Mod : a;
}
int Mul(int a, int b){
return 1ll * a * b % Mod;
}
int Qpow(int a, int b){
int ans = 1;
while(b){
if(b & 1) ans = Mul(ans, a);
a = Mul(a, a), b >>= 1;
}
return ans;
}
int C(int n, int m){
if(m > n) return 0;
return Mul(fac[n], Mul(inv[m], inv[n - m]));
}
int main(){
n = read(), m = read();
fac[0] = inv[0] = 1;
rep(i, 1, n) fac[i] = Mul(fac[i - 1], i), inv[i] = Qpow(fac[i], Mod - 2);
rep(i, 1, n){
f[i] = g[i] = Qpow(2, i * (i - 1) / 2);
rep(j, 1, i - 1) f[i] = Dec(f[i], Mul(f[j], Mul(g[i - j], C(i - 1, j - 1))));
}
rep(i, 1, n) dp[i][1] = f[i];
rep(i, 2, n) rep(j, 2, i) rep(k, 1, i){
dp[i][j] = Inc(dp[i][j], Mul(f[k], Mul(dp[i - k][j - 1], C(i - 1, k - 1))));
}
printf("%d", m == 1 ? dp[n][m] - 1 : dp[n][m]);
return 0;
}