AtCoder Regular Contest 115 D

这篇博客主要介绍了如何解决图论问题中关于寻找恰好拥有奇数度节点的生成子图数量的算法。首先,当图是一棵树时,如果k为奇数则答案为0,k为偶数时答案为Cnk。如果是联通块,可以通过生成树加上剩余边的选择来计算。对于多个联通块,采用多项式乘法的方法。博主给出了完整的C++代码实现,并在每个连通分量中分别计算并组合答案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

D - Odd Degree

题意: 给一个点数为n,边数为m的图。求恰好k个点度数为奇数的生成子图的个数 ,其中k的范围 [ 0 , n ] [0,n] [0,n]

思路:

图为一颗树时: 当k为奇数时 ,答案为0。因为所有点的度数之和为 2 ∗ m 2*m 2m,每加一条边就会有两个点的度数增加,也就是两个点的度数的奇偶值同时变化,也就不存在单独的点度数为奇数了; 当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} 2xCnk

图为多个联通块时: 对于每一个连通图求出答案后,把点的个数与相应的答案数映射到多项式的系数与指数,然后模拟多项式相乘 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]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值