问题分析
可以给小树钦定一个根, \(Dp[i][j]\) 表示大树上的点 \(i\) 对应到小树上的点 \(j\) 的可能的方案数。然后每一步转移都是一个状压DP(将小树是否被匹配状压,然后枚举大树上的点和小树上的点匹配)。
但如果这样统计的话,在两种情况下有重复:
- 在小树取不同的根但仍同构;
- 确定小树的根后,小树的子树同构。
所以我们对钦定根后的小树进行哈希,即可排除第一种重复。而如果小树的某两个子树同构,那么就在统计的时候强行钦定一个顺序,这样就解决了第二种重复。
参考程序
#include <cstdio> #include <cstring> #include <algorithm> #include <set> const int Maxn = 2000; const int Maxm = 12; const int MaxAlpha = 1 << Maxm; int Mod = 1e9 + 7; struct edge { int To, Next; edge() {} edge(int _To, int _Next) : To(_To), Next(_Next) {} }; struct node { int Value, Index; node() {} node(int _Value, int _Index) : Value(_Value), Index(_Index) {} inline bool operator<(const node Other) const { return Value < Other.Value; } }; edge Edge1[Maxn << 1], Edge2[Maxm << 1]; int n, m, Ans; int Start1[Maxn + 1], Start2[Maxm + 1], Used1, Used2; int Hash[Maxm + 1], Father[Maxm + 1], Size[Maxm + 1]; int Dp[Maxn + 1][Maxm + 1], F[2][MaxAlpha]; node Temp[Maxm + 1]; int Ctrl[Maxm + 1]; std::set<int> Set; inline void AddEdge1(int x, int y); inline void AddEdge2(int x, int y); inline void Init(); void GetHash(int u, int Fa); void Calc(int u, int Fa); int main() { Init(); for (int i = 1; i <= m; ++i) { GetHash(i, 0); if (Set.count(Hash[i])) continue; Set.insert(Hash[i]); memset(Dp, 0, sizeof(Dp)); Calc(1, 0); for (int j = 1; j <= n; ++j) Ans = (Ans + Dp[j][i]) % Mod; } printf("%d\n", Ans); return 0; } inline void AddEdge1(int x, int y) { Edge1[++Used1] = edge(y, Start1[x]); Start1[x] = Used1; return; } inline void AddEdge2(int x, int y) { Edge2[++Used2] = edge(y, Start2[x]); Start2[x] = Used2; return; } inline void Init() { scanf("%d", &n); for (int i = 1; i < n; ++i) { int x, y; scanf("%d%d", &x, &y); AddEdge1(x, y); AddEdge1(y, x); } scanf("%d", &m); for (int i = 1; i < m; ++i) { int x, y; scanf("%d%d", &x, &y); AddEdge2(x, y); AddEdge2(y, x); } return; } void GetHash(int u, int Fa) { Size[u] = 1; Father[u] = Fa; for (int t = Start2[u]; t; t = Edge2[t].Next) { int v = Edge2[t].To; if (v == Fa) continue; GetHash(v, u); Size[u] += Size[v]; } int Count = 0; for (int t = Start2[u]; t; t = Edge2[t].Next) { int v = Edge2[t].To; if (v == Fa) continue; Temp[++Count] = node(Hash[v], v); } std::sort(Temp + 1, Temp + Count + 1); Hash[u] = 0; for (int i = 1; i <= Count; ++i) { Hash[u] <<= Size[Temp[i].Index] << 1; Hash[u] += Hash[Temp[i].Index]; } Hash[u] <<= 1; Hash[u] += 1 << ((Size[u] << 1) - 1); return; } void Calc(int u, int Fa) { for (int t = Start1[u]; t; t = Edge1[t].Next) { int v = Edge1[t].To; if (v == Fa) continue; Calc(v, u); } for (int uu = 1; uu <= m; ++uu) { if (Size[uu] == 1) { Dp[u][uu] = 1; continue; } int Count = 0; memset(Ctrl, 0, sizeof(Ctrl)); for (int t = Start2[uu]; t; t = Edge2[t].Next) { int v = Edge2[t].To; if (v ==Father[uu]) continue; Temp[++Count] = node(Hash[v], v); } std::sort(Temp + 1, Temp + Count + 1); for (int i = 2; i <= Count; ++i) if (Temp[i].Value == Temp[i - 1].Value) Ctrl[i] = 1; memset(F, 0, sizeof(F)); F[0][0] = 1; int Step = 0; for (int t = Start1[u]; t; t = Edge1[t].Next) { int v = Edge1[t].To; if (v ==Fa) continue; for (int j = 0; j < 1 << Count; ++j) F[(Step + 1) & 1][j] = 0; for (int j = 0; j < 1 << Count; ++j) for (int k = 1; k <= Count; ++k) { if ((j >> (k - 1)) & 1) continue; if (Ctrl[k] && ((j >> (k - 2)) & 1) == 0) continue; F[(Step + 1) & 1][j | (1 << (k - 1))] += 1LL * F[Step & 1][j] * Dp[v][Temp[k].Index] % Mod; F[(Step + 1) & 1][j | (1 << (k - 1))] %= Mod; } for (int j = 0; j < 1 << Count; ++j) { F[(Step + 1) & 1][j] += F[Step & 1][j]; F[(Step + 1) & 1][j] %= Mod; } ++Step; } Dp[u][uu] = F[Step & 1][(1 << Count) - 1]; } return; }