问题分析
可以给小树钦定一个根, \(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;
}