【LOJ2250】「ZJOI2017」仙人掌

该博客介绍了LOJ2250题目的解法,涉及ZJOI2017年的一道仙人掌图问题。当给定的图被视为大小为N+1的点的菊花图时,通过转移方程dpi=dpi-1+(i-1)dpi-2可以计算方案数。由于不允许重边,合法加边方案中边的长度至少为2。通过特定转化,将边转化为长度为2的新边,问题转换为求解树的连边方案。答案是各节点度数的连乘积,时间复杂度为O(N+M)。

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

【题目链接】

【思路要点】

  • 考虑给定的图为大小为 N + 1 N+1 N+1 的点的菊花图的情况,记方案数为 d p N dp_N dpN ,考虑最后一个儿子是否连边,则有转移 d p i = d p i − 1 + ( i − 1 ) d p i − 2 dp_i=dp_{i-1}+(i-1)dp_{i-2} dpi=dpi1+(i1)dpi2
  • 断开环边,剩余问题即为如何解决图是一棵树的情况。
  • 注意到由于不允许存在重边,一个合法的加边方案中边的长度 ≥ 2 \geq2 2
  • 考虑对一个合法的加边方案进行如下转化:记一条边 ( x , y ) (x,y) (x,y) 经过了点 x , a , b , c , … , d , e , f , y x,a,b,c,\dots,d,e,f,y x,a,b,c,,d,e,f,y ,则断开 ( x , y ) (x,y) (x,y) ,连接 ( x , b ) , ( a , c ) , … , ( d , f ) , ( e , y ) (x,b),(a,c),\dots,(d,f),(e,y) (x,b),(a,c),,(d,f),(e,y)
  • 得到的连边方案所连的新边长度均为 2 2 2 ,且与将各个点与其邻点看做一个菊花图的连边方案的组合一一对应。
  • 因此,记 d i d_i di i i i 的度数,答案即为 ∏ i = 1 N d p d i \prod_{i=1}^{N}dp_{d_i} i=1Ndpdi
  • 时间复杂度 O ( N + M ) O(N+M) O(N+M)

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
const int P = 998244353;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, cnt[MAXN], dp[MAXN];
bool vis[MAXN], ban[MAXN], ins[MAXN], flg;
vector <pair <int, int>> a[MAXN];
void dfs(int pos, int fa, int num) {
	cnt[pos] = 0;
	vis[pos] = true;
	ins[pos] = true;
	for (auto x : a[pos])
		if (!vis[x.first]) {
			dfs(x.first, pos, x.second);
			cnt[pos] += cnt[x.first];
		} else if (ins[x.first] && x.first != fa) {
			cnt[pos]++, cnt[x.first]--;
			ban[x.second] = true;
		}
	ins[pos] = false;
	if (cnt[pos] >= 2) flg = true;
	if (cnt[pos] >= 1) ban[num] = true;
}
int main() {
	int T; read(T);
	while (T--) {
		read(n), read(m);
		for (int i = 1; i <= n; i++) {
			a[i].clear();
			vis[i] = false;
		}
		for (int i = 1; i <= m; i++) {
			int x, y; read(x), read(y);
			a[x].emplace_back(y, i);
			a[y].emplace_back(x, i);
			ban[i] = false;
		}
		flg = false;
		dfs(1, 0, 0);
		if (flg) {
			puts("0");
			continue;
		}
		dp[0] = dp[1] = 1;
		for (int i = 2; i <= n; i++)
			dp[i] = (dp[i - 1] + 1ll * (i - 1) * dp[i - 2]) % P;
		int ans = 1;
		for (int i = 1; i <= n; i++) {
			int cnt = 0;
			for (auto x : a[i])
				if (!ban[x.second]) cnt++;
			ans = 1ll * ans * dp[cnt] % P;
		}
		writeln(ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值