[BZOJ4784][Zjoi2017]仙人掌(树形DP)

Address

洛谷P3687
BZOJ4784
UOJ#290
LOJ#2250

Solution

首先,如果原图不是仙人掌,就直接输出 0 0
否则对图进行一遍 DFS ,找出所有的环并去掉,原图变成森林。答案显然是每棵树的答案的乘积。
考虑到在如果在树上的点对 ( u , v ) (u,v) 之间连边,就相当于把 u u v v 的路径上的所有边标记为环边。
但存在两个问题:
(1)不能有重边,也就是说如果树上已经有边 ( u , v ) (u,v) 就不能连边 ( u , v ) (u,v)
(2)每一条边不一定要全部被标记为环边。
但撕烤后发现上面这两个条件可以抵消。
也就是说,如果我们假设可以有重边,那么问题再次转化成选出一些路径集合 { ( u , v ) u v } \{(u,v)|u\ne v\} 覆盖树上所有的边。
我们可以开始愉快地树形 DP 了!
f [ u ] f[u] 表示覆盖 u u 的子树内所有边的方案数。
g [ u ] g[u] 表示覆盖 u u 的所有子树内所有边,并选出一条端点为 u u 的路径向 u u 的父亲延伸的方案数。
w [ i ] w[i] 表示将 i i 个元素分到一些集合内,使得每个集合非空且大小不超过 2 2 的方案数。
h [ i ] h[i] 表示将 i i 个元素分到一些集合内,使得每个集合非空且大小不超过 2 2 ,所有方案中大小为 1 1 的集合个数之和。
边界:
w [ 0 ] = 1 , h [ 0 ] = 0 , f [ ] = 0 , g [ ] = 0 w[0]=1,h[0]=0,f[叶子节点]=0,g[叶子节点]=0
w w 的转移:
w [ i ] = w [ i 1 ] + w [ i 2 ] × ( i 1 ) w[i]=w[i-1]+w[i-2]\times(i-1)
h h 的转移:
h [ i ] = h [ i 1 ] + w [ i 1 ] + h [ i 2 ] × ( i 1 ) h[i]=h[i-1]+w[i-1]+h[i-2]\times(i-1)
f f 的转移也就是枚举 u u 的子节点 v v ,讨论两种情况(一为路径 ( u , v ) (u,v) 单独存在,二为路径 ( u , v ) (u,v) 不单独存在),然后需要把子节点分组,每个组大小不超过 2 2 ,对于大小为 2 2 的组,在点 u u 把来自两条处于不同子树的路径连接成一条:
f [ u ] = w [ d [ u ] ] v s o n [ u ] ( f [ v ] + g [ v ] ) f[u]=w[d[u]]\prod_{v\in son[u]}(f[v]+g[v])
g g 也差不多:
g [ u ] = h [ d [ u ] ] v s o n [ u ] ( f [ v ] + g [ v ] ) g[u]=h[d[u]]\prod_{v\in son[u]}(f[v]+g[v])
答案为所有森林的根的 f f 之积。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
#define Tree(u) for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu)
using namespace std;

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

const int N = 5e5 + 5, M = N << 2, ZZQ = 998244353;
int n, m, ecnt, nxt[M], adj[N], go[M], f[N], g[N], h[N], w[N], cnt[N],
dfn[N], T, d[N];
bool is, cut[M], vis[N];

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
	nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u;
}

void dfs(int u, int fe)
{
	dfn[u] = ++T;
	cnt[u] = 0;
	Edge(u)
	{
		if (e == (fe ^ 1)) continue;
		if (!dfn[v]) dfs(v, e), cnt[u] += cnt[v];
		else if (dfn[u] < dfn[v]) cnt[u]--;
		else cnt[u]++;
	}
	if (u > 1 && !cnt[u]) cut[fe] = cut[fe ^ 1] = 1;
	if (cnt[u] >= 2) is = 0;
}

int dp(int u, int fu)
{
	d[u] = 0;
	f[u] = 1; vis[u] = 1;
	Tree(u)
	{
		if (!cut[e]) continue;
		d[u]++;
		dp(v, u);
		f[u] = 1ll * f[u] * (f[v] + g[v]) % ZZQ;
	}
	g[u] = 1ll * f[u] * h[d[u]] % ZZQ;
	f[u] = 1ll * f[u] * w[d[u]] % ZZQ;
	return f[u];
}

void work()
{
	int i, x, y, ans = 1;
	n = read(); m = read();
	ecnt = 1; T = 0;
	For (i, 1, n) adj[i] = dfn[i] = 0, vis[i] = 0;
	while (m--) x = read(), y = read(), add_edge(x, y);
	For (i, 2, ecnt) cut[i] = 0;
	is = 1;
	dfs(1, 0);
	if (!is) return (void) puts("0");
	For (i, 1, n) if (!vis[i])
		ans = 1ll * ans * dp(i, 0) % ZZQ;
	printf("%d\n", ans);
}

int main()
{
	int i, T = read();
	w[0] = 1;
	For (i, 1, 500000)
	{
		w[i] = w[i - 1];
		if (i > 1) w[i] = (w[i] + 1ll * (i - 1) * w[i - 2] % ZZQ) % ZZQ;
		h[i] = (h[i - 1] + w[i - 1]) % ZZQ;
		if (i > 1) h[i] = (h[i] + 1ll * (i - 1) * h[i - 2] % ZZQ) % ZZQ;
	}
	while (T--) work();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/83049686