【刷题】【省选】ZJOI2017_仙人掌_LOJ2250/Luogu3687_圆方树/dp计数/树形dp

链接

LOJ2250
Luogu3687

题解

  • 先把环全都扔掉(字面意思),因为你不可能再在环上加边。但是在这个过程中你要科学判断读入的是否是圆方树。(这个东西我调了半天)
  • 然后就是树上问题。
  • 转化成这样:每次选不相邻的两个点,将这两点间的简单路径上的每条边覆盖上。求每条边最多被覆盖一次的方案数。
  • 记\(f_{i,0}\)表示\(i\)号点子树内部全都消化完了的方案数,\(f_{i,1}\)表示\(i\)号点子树中还能有一个不是从\(i\)出发的向上路径的方案数。再记\(i\)有\(ch(i)\)个孩子。
  • 我们发现可以转换成一个序列问题。每个点选了有一个权值,不选有一个权值,你可以将所有选了的点任意两两配对,一个方案的权值就是他所有点的权值的积。\(f_{i,0}\)就是所有方案的权值之和。
  • 点\(j\)选了的权值是什么?\(f_{j,0}+f_{j,1}\);不选的权值是什么?\(f_{j,0}+f_{j,1}\)。他们竟然一样!(我写的时候在这里卡了半天,因为我压根没有去想他们的权值会一样)。
  • 我们只用算有多少种选择及配对方案。记\(g_i\)表示\(i\)个点的选择方案,显然有\(g_{i}=g_{i-1}+(i-1)g_{i-2},g_0=g_1=1\)。(我又在这里卡了半天,因为我一上来就莽式子,发现那是一大坨组合数)
  • 好,\(f_{i,0}\)算完了。\(f_{i,0}=h_{ch(i)}\prod (f_{j,0}+f_{j,1})\)
  • 至于\(f_{i,1}\),我们发现我们只要从他的孩子中选择一个点\(j\),让他一枝独秀,可以往上走就行了。我们惊奇地发现\(j\)内部可以被选择往上走的方案数依旧是\(f_{j,0}+f_{j,1}\)。
  • 那,\(f_{i,1}\)也算完了。\(f_{i,1}=h_{ch(i)-1}ch(i)\prod (f_{j,0}+f_{j,1})\)。
  • 你过题了,你拿到了浙江省选的100分,并最终获得了标准分进队的殊荣。让我们恭喜你。

代码

#include<bits/stdc++.h>
#define LL long long
#define MAXN 1000000
#define MAXM 1000000
#define MOD 998244353
using namespace std;
template<typename T>void Read(T &cn)
{
	char c;int sig = 1;
	while(!isdigit(c = getchar()))if(c == '-')sig = -1; cn = c-48;
	while(isdigit(c = getchar()))cn = cn*10+c-48; cn*=sig;
}
template<typename T>void Write(T cn)
{
	if(cn < 0) {putchar('-'); cn = 0-cn; }
	int wei = 0; T cm = 0; int cx = cn%10; cn/=10;
	while(cn)cm = cm*10+cn%10,cn/=10,wei++;
	while(wei--)putchar(cm%10+48),cm/=10;
	putchar(cx+48);
}
template<typename T>void Min(T &cn, T cm) {cn = cn < cm ? cn : cm; }
struct Tree{
	struct qwe{
		int a,b,ne;
		void mk(int cn, int cm, int cx) {a = cn; b = cm; ne = cx; }
	};
	qwe a[MAXM+1];
	int alen;
	int head[MAXN+1];
	void build(int cn) {alen = 0; for(int i = 1;i<=cn;i++) head[i] = 0; }
	void lian(int cn, int cm) {a[++alen].mk(cn,cm,head[cn]); head[cn] = alen; }
	void lian_d(int cn, int cm) {lian(cn, cm); lian(cm, cn); }
}T1, T2;
int n,m,t;
int dfn[MAXN+1], low[MAXN+1], zhan[MAXN+1], guo[MAXN+1], shi, zlen;
int vis[MAXN+1];
LL f[MAXN+1][2];
LL h[MAXN+1];
void yuchu(int cn) {h[0] = h[1] = 1; for(int i = 2;i<=cn;i++) h[i] = (h[i-1]+1ll*(i-1)*h[i-2])%MOD; }
int tarjan(int cn, int fa)
{
	guo[cn] = 0;
	dfn[cn] = low[cn] = ++shi;
	zhan[++zlen] = cn;
	for(int i = T1.head[cn];i;i = T1.a[i].ne)
	{
		int y = T1.a[i].b;
		if(y == fa) continue;
		if(dfn[y]) {
			Min(low[cn], dfn[y]);
			if(guo[cn] && guo[y] != cn) return -1;
			if(guo[y] != cn) guo[cn] = y;
		}
		else {
			int lin = zlen;
			int lin2 = tarjan(y, cn);
			Min(low[cn], low[y]);
			if(lin2 == -1) return -1;
			if(low[y] >= dfn[cn]) {
				if(lin2 != cn && lin2 != 0) return -1;
				if(zlen == lin+1) T2.lian_d(cn, zhan[zlen]);
				zlen = lin;
			}
			else {
				if(lin2 && guo[cn]) return -1;
				if(!guo[cn]) guo[cn] = lin2;
			}
		}
	}
	return guo[cn];
}
void dfs(int cn, int fa)
{
	vis[cn] = 1; 
	f[cn][0] = 1;
	int ge = 0;
	for(int i = T2.head[cn];i;i = T2.a[i].ne) 
	{
		int y = T2.a[i].b;
		if(y == fa) continue;
		dfs(y, cn);
		f[cn][0] = 1ll*f[cn][0]*(f[y][1] + f[y][0])%MOD;
		ge++;
	}
	f[cn][1] = f[cn][0] * h[ge-1]%MOD *ge%MOD;
	f[cn][0] = f[cn][0] * h[ge]%MOD;
}
void zuo()
{
	Read(n); Read(m);
	T1.build(n); T2.build(n);
	for(int i = 1;i<=m;i++) {int bx,by; Read(bx); Read(by); T1.lian_d(bx,by); }
	shi = zlen = 0; for(int i = 1;i<=n;i++) dfn[i] = low[i] = 0;
	if(tarjan(1, 0) == -1) {puts("0"); return; }
	LL ans = 1; for(int i = 1;i<=n;i++) vis[i] = 0;
	for(int i = 1;i<=n;i++) if(!vis[i]) dfs(i, 0), ans = ans*f[i][0]%MOD;
	Write(ans); puts("");
}
int main() {yuchu(MAXN); Read(t); while(t--) zuo(); return 0; }
上一篇:k8s之ConfigMap


下一篇:typora的css样式