题解 数树

传送门

神仙题

发现 \(m\) 很小,肯定是要状压的,考虑怎么压
最终我们希望知道对于B的每一种形态,它在A里能匹配多少次
于是尝试枚举B的形态,对于每种形态:

  • 对于树同构当点数较小时的一种判定/计数方法:
    \(f_{i, j}\) 表示A中第 \(i\) 个点匹配上B中第 \(j\) 个点的方案数
    发现点 \(i\) 如果能匹配点 \(j\),那点 \(i\) 的儿子一定要存在一种情况能与 \(j\) 的儿子一一形成匹配
    于是状压,令 \(g_{i, s}\) 为点 \(i\) 的一部分儿子匹配上了B的 \(s\) 集合中的点的方案数
    于是 \(f_{i, j}=g_{i, son_j}\)
    若是计数,对A中每个点匹配上B中根节点的方案数求和即可
    但发现一个小事情:B可能是自同构的,这样会算重
    于是再除以B的自同构方案数即可
    这个方案数可以阶乘爆算,也可以像上面一样自己和自己匹配一遍得到
Code:
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define ll long long
#define fir first
#define sec second
#define make make_pair
//#define int long long

char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
	int ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m;
ll ans;
int son[12], lim, p[N];
const ll mod=998244353;
bool mp[12][12];
pair<int, int> e2[N];
ll f[3010][11], g[3010][1<<10], tem[1<<10];
inline void md(ll& a, ll b) {a+=b; a=a>=mod?a-mod:a;}
inline ll qpow(ll a, ll b) {ll ans=1; for (; b; a=a*a%mod, b>>=1) if (b&1) ans=ans*a%mod; return ans;}
namespace edge1{
	int head[N], size;
	struct edge{int to, next;}e[N];
	inline void add(int s, int t) {e[++size].to=t; e[size].next=head[s]; head[s]=size;}
}
namespace edge2{
	int head[N], size;
	struct edge{int to, next;}e[N];
	inline void add(int s, int t) {e[++size].to=t; e[size].next=head[s]; head[s]=size;}
}

void dfs1(int u, int fa) {
	using namespace edge2;
	for (int i=head[u],v; ~i; i=e[i].next) {
		v = e[i].to;
		if (v!=fa) {
			son[u]|=1<<(v-1);
			dfs1(v, u);
		}
	}
}

void dfs2(int u, int fa) {
	using namespace edge1;
	g[u][0]=1;
	for (int i=head[u],v; ~i; i=e[i].next) {
		v = e[i].to;
		if (v==fa) continue;
		dfs2(v, u);
		for (int s=0; s<lim; ++s) tem[s]=g[u][s];
		for (int j=1; j<=m; ++j) if (f[v][j]) {
			for (int s=lim-1; ~s; --s) if (!(s&(1<<(j-1)))) {
				tem[s|(1<<(j-1))]=(tem[s|(1<<(j-1))]+g[u][s]*f[v][j])%mod;
			}
		}
		for (int s=0; s<lim; ++s) g[u][s]=tem[s];
	}
	for (int i=1; i<=m; ++i) f[u][i]=g[u][son[i]];
}

signed main()
{
	freopen("count.in", "r", stdin);
	freopen("count.out", "w", stdout);

	n=read();
	memset(edge1::head, -1, sizeof(edge1::head));
	memset(edge2::head, -1, sizeof(edge2::head));
	for (int i=1,u,v; i<n; ++i) {
		u=read(); v=read();
		edge1::add(u, v); edge1::add(v, u);
	}
	m=read(); lim=1<<m;
	for (int i=1,u,v; i<m; ++i) {
		u=read(); v=read();
		edge2::add(u, v); edge2::add(v, u);
		e2[i]=make(u, v);
		mp[u][v]=mp[v][u]=1;
	}
	for (int i=1; i<=m; ++i) {
		// cout<<"i: "<<i<<endl;
		memset(son, 0, sizeof(son));
		dfs1(i, 0);
		memset(f, 0, sizeof(f));
		memset(g, 0, sizeof(g));
		dfs2(1, 0);
		for (int j=1; j<=n; ++j) md(ans, f[j][i]);
	}
	int cnt=0;
	for (int i=1; i<=m; ++i) p[i]=i;
	do {
		for (int i=1; i<m; ++i) if (!mp[p[e2[i].fir]][p[e2[i].sec]]) goto jump;
		++cnt;
		jump: ;
	} while (next_permutation(p+1, p+m+1));
	printf("%lld\n", ans*qpow(cnt, mod-2)%mod);

	return 0;
}
posted @ 2021-10-28 08:08  Administrator-09  阅读(0)  评论(0编辑  收藏  举报