D - Odd Degree
题意: 给一个点数为n,边数为m的图。求恰好k个点度数为奇数的生成子图的个数 ,其中k的范围 [ 0 , n ] [0,n] [0,n]
思路:
图为一颗树时: 当k为奇数时 ,答案为0。因为所有点的度数之和为 2 ∗ m 2*m 2∗m,每加一条边就会有两个点的度数增加,也就是两个点的度数的奇偶值同时变化,也就不存在单独的点度数为奇数了; 当k为偶数时 ,答案为 C n k C_{n}^{k} Cnk 。可以知道对于一个图中任意的k个点都可以构造出度数为奇数的生成子图。任意选择即可。
图为一整个联通块时: 跟图为一棵树的情况很像,先划分出一个生成树,剩下的x条边就是选与不选的情况,直接将答案乘以 2 x 2^x 2x 即可。即 : 2 x ∗ C n k 2^x * C_{n}^{k} 2x∗Cnk
图为多个联通块时: 对于每一个连通图求出答案后,把点的个数与相应的答案数映射到多项式的系数与指数,然后模拟多项式相乘 O ( n 2 ) O(n^2) O(n2) 统计答案即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 5005;
const int mod = 998244353;
typedef long long ll;
ll fac[N];
ll qpow(ll x, ll n) {
ll res = 1;
for (; n; n >>= 1, x = x * x % mod)
if (n & 1) res = res * x % mod;
return res;
}
ll inv(ll a) {
return qpow(a, mod-2)%mod;
}
void solve() {
fac[0] = 1;
for(int i = 1;i < N; i++) {
fac[i] = (fac[i-1]*i)%mod;
}
}
ll comb(ll n, ll k) {
if(k > n) return 0;
if(k == 1) return n;
return (fac[n]*inv(fac[k])%mod * inv(fac[n-k])%mod);
}
int n, m;
vector <int> g[N];
bool vis[N];
int nump, numl;
void dfs(int p) {
vis[p] = 1;
nump++; numl += g[p].size();
for(int son: g[p]) {
if(!vis[son])
dfs(son);
}
}
ll f[N], ans[N], a[N], tmp[N];
int main() {
solve();
scanf("%d %d", &n, &m);
for(int i = 0; i < m; i++) {
int u, v; scanf("%d %d", &u, &v);
g[u].push_back(v); g[v].push_back(u);
}
f[0] = 1;
for(int i = 1; i < N; i++) f[i] = (f[i-1] * 2ll) % mod;
bool nice = 0;
int sum = 0;
for(int i = 1; i <= n; i++) {
if(!vis[i]) {
nump = 0; numl = 0;
dfs(i);
numl /= 2;
for(int k = 0; k <= nump; k += 2) {
a[k] = (f[numl-(nump-1)] * comb(nump, k)) % mod;
}
if(!nice) {
nice = 1;
for(int j = 0; j <= nump; j++) ans[j] = a[j];
}
else {
for(int j = sum-(sum&1); j >= 0; j -= 2) {
for(int k = 0; k <= nump; k += 2) {
tmp[j+k] = (tmp[j+k] + ans[j] * a[k] % mod) % mod;
}
}
memcpy(ans, tmp, sizeof(tmp));
memset(tmp, 0, sizeof(tmp));
}
sum += nump;
}
}
for(int i = 0; i <= n; i++) {
printf("%lld\n", ans[i]);
}
}