跳转至

D - Odd Degree

题意

N 頂点 M 辺の単純無向グラフが与えられます。頂点には 1,\ldots,N の番号がついています。i 番目の辺は頂点 A_i と頂点 B_i を結んでいます。このグラフの全域部分グラフ(※)であって、次数が奇数の頂点がちょうど K 個であるものの個数をすべての K (0≤K≤N) について求めてください。ただし答えは非常に大きくなる可能性があるので、998244353 で割った余りを出力してください。

(※)G の部分グラフ HG の全域部分グラフであるとは、H の頂点集合が G の頂点集合と等しく、H の辺集合が G の辺集合の部分集合であることをいいます。

解析

首先,由于每一条边都对应 2 度数,所有点的度数和一定是偶数,即不存在含有奇数个度数为奇数节点的图。

对于一棵树,若指定了哪些点的度数为奇数 (这些点的数量为偶数),而其他的点度数为偶数,那该树总是存在恰好 1 个满足这样条件的生成子图。因为那些与叶子节点相邻的边是否保留是确定的,计算完它们对非叶节点的影响后将其和叶子结点删去,就可以得到该问题的子问题。

对于一个连通块,保留一个生成树,其余的边任意选,为了得到指定的奇数度数节点集,生成树的选法是唯一的。故一个大小为 N,包含 M 条边的连通块的包含恰好 K (2\mid K) 个奇度数节点的生成子图数为 2^{M-N+1}\binom{N}{K}

对于含多个连通块的图,将各个连通块的答案卷积即可。

实现

#include <bits/stdc++.h>

typedef long long int ll;
const int maxn = 5e3 + 19, mod = 998244353;

std::vector<int> operator*(const std::vector<int> &a, const std::vector<int> &b){
    std::vector<int> res(a.size() + b.size() - 1);
    for(int i = 0; i < (int)a.size(); ++i)
        for(int j = 0; j < (int)b.size(); ++j)
            res[i + j] = (res[i + j] + (ll)a[i] * b[j]) % mod;
    return res;
}

int n, m;

int fa[maxn], size[maxn], deg[maxn];
int find(int node){return fa[node] == node ? node : fa[node] = find(fa[node]); }

void exgcd(int a, int b, int &x, int &y){
    if(!b) return (void)(x = 1, y = 0);
    exgcd(b, a % b, y, x), y -= a / b * x;
}

int fact[maxn], ifact[maxn], p2[maxn];
void init(void){
    p2[0] = 1;
    for(int i = 1; i <= m; ++i) p2[i] = (p2[i - 1] + p2[i - 1]) % mod;
    fact[0] = 1;
    for(int i = 1; i <= n; ++i) fact[i] = (ll)fact[i - 1] * i % mod;
    exgcd(fact[n], mod, ifact[n], ifact[0]), ifact[n] = (ifact[n] % mod + mod) % mod;
    for(int i = n - 1; i >= 0; --i) ifact[i] = (ll)ifact[i + 1] * (i + 1) % mod;
}

inline int binom(int n, int m){
    return (ll)fact[n] * ifact[n - m] % mod * ifact[m] % mod;
}

int main(){
    std::scanf("%d%d", &n, &m);
    std::iota(fa, fa + 1 + n, 0);
    std::fill(size + 1, size + 1 + n, 1);
    init();
    for(int i = 1, u, v; i <= m; ++i){
        std::scanf("%d%d", &u, &v);
        if(find(u) == find(v)) ++deg[find(u)];
        else{
            size[find(v)] += size[find(u)];
            deg[find(v)] += deg[find(u)] + 1;
            fa[find(u)] = find(v);
        }
    }
    std::vector<int> ans(1, 1);
    for(int i = 1; i <= n; ++i)
        if(find(i) == i){
            static std::vector<int> tmp; tmp.clear();
            tmp.resize(size[i] / 2 + 1);
            for(int j = 0; j < (int)tmp.size(); ++j)
                tmp[j] = (ll)binom(size[i], j * 2) * p2[deg[i] - size[i] + 1] % mod;
            ans = ans * tmp;
        }
    for(int i = 0; i <= n; ++i)
        if((i & 1) || (i >> 1) >= (int)ans.size()) std::puts("0");
        else std::printf("%d\n", ans[i >> 1]);
    return 0;
}

来源

AtCoder Regular Contest 115 D - Odd Degree

评论