给定两棵树 S, T,问 S 中有多少连通子图同构于 T。优化
Input
第一行一个整数 |S| (1 ≤ |S| ≤ 1000),表示 S 中的结点个数。
接下来 |S|-1 行,每行两个整数 ui, vi (1 ≤ ui, vi ≤ |S|),描述了 S 中的边。
接下来一个整数 |T| (1 ≤ |T| ≤ 12) 描述了 T 中的结点个数。
接下来 |T|-1 行,每行两个整数 xi, yi (1 ≤ xi, yi ≤ |T|),描述了 T 中的边。ui
Output
输出一行一个整数,表示连通子图数量 mod 10^9 + 7。spa
Examples
Input
5
1 2
2 3
3 4
4 5
3
1 2
2 3
Output
3code
Input
3
2 3
3 1
3
1 2
1 3
Output
1ip
将 S 转为有根树。考虑在连通子图的最高点统计该连通子图的贡献。
枚举 T 中每个点做为根(注意要使用树哈希判断是否以前枚举过同构的状况),而后计算此时的贡献。get
假设 x 为连通子图的最高点。此时由于 T 的根也已经肯定,那么父子顺序不会改变。
至关于从 x 中选择一些子树去匹配 T 的根下面的全部子树。这就很是有 dp 的样子了。
记 dp(x, s) 表示以 x 为根,匹配 T 中以 s 为根的子树的方案数。转移的时候枚举儿子是对应 T 中的哪个儿子(或者是空,即一个都不对应)。
可是这个转移太慢了,咱们须要同时枚举 x 下的全部子树的对应状况。it
考虑优化,即每次加入 x 下的一棵子树,更新状态。
那么 s 的定义就拓宽了:s 能够是 T 中某一结点 p + p 的某些子树。
此时只须要再枚举 x 的这棵子树选成 s1 这一状态,经过某种方法将 s 与 s1 变成新的 s' 就能够实现转移了。io
看上去复杂度不太对?感受这个确定会 TLE?
注意 s 的定义,能够等价于选一个点 p,而后删去 p 的某些往外延伸的子树(这些子树包括父亲那个方向的)。
那么 s 的范围应该是 O(2^m)。class
转移的时候,由于同时是在 T 的某个结点 p 之下加入某一子树,那么可能性只有 p 的儿子个数那么多。
即对于每一个状态,只会有 O(m) 种转移。
处理出这 O(m) 种可能的转移,便可作到 O(nm2^m) 的 dp 复杂度,足以经过本题(并且还卡不满,由于有不少同构的子树)。
用树哈希 + map 能够将一个子树映射成一个整数 id。
咱们能够先对 T 的全部结点为根进行 dfs 处理合法的状态与转移,再对 S 进行 dp。
#include <map> #include <set> #include <cstdio> #include <vector> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; #define repG(G, x) for(Graph::edge *p = G.adj[x];p;p = p->nxt) #define repS(S) for(set<int>::iterator it = S.begin();it != S.end();it++) #define fi first #define se second #define mp make_pair typedef pair<int, int> pii; typedef unsigned long long ull; const int MAXN = 1000; const int MAXM = 12; const int MAXK = (1<<MAXM); const int MOD = int(1E9) + 7; struct Graph{ struct edge{ int to; edge *nxt; }edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt; Graph() {ecnt = edges;} void addedge(int u, int v) { edge *p = (++ecnt); p->to = v, p->nxt = adj[u], adj[u] = p; p = (++ecnt); p->to = u, p->nxt = adj[v], adj[v] = p; } }S, T; map<ull, int>m1; int hcnt; int id(ull x) { if( m1.count(x) ) return m1[x]; else return m1[x] = (++hcnt); } ull r[MAXM + 5], h[MAXM + 5]; int s[MAXM + 5]; set<int>st2[MAXK + 5]; vector<pii>trans[MAXK + 5]; vector<int>a; void dfs1(int x, int fa) { s[x] = h[x] = 1; repG(T, x) { if( p->to == fa ) continue; dfs1(p->to, x), h[x] += r[s[p->to]] * h[p->to], s[x] += s[p->to]; } a.clear(); repG(T, x) { if( p->to == fa ) continue; a.push_back(p->to); } int k = a.size(), t = (1<<k); for(int s1=0;s1<t;s1++) { ull hsh = 1; for(int p=0;p<k;p++) if( (s1 >> p) & 1 ) hsh += r[s[a[p]]] * h[a[p]]; int x = id(hsh); for(int p=0;p<k;p++) if( !((s1 >> p) & 1) ) { int y = id(h[a[p]]); if( !st2[x].count(y) ) { st2[x].insert(y); trans[x].push_back(mp(y, id(hsh + r[s[a[p]]] * h[a[p]]))); } } } } int f[MAXN + 5][MAXK + 5], g[MAXK + 5]; void dfs2(int x, int fa) { f[x][1] = 1; repG(S, x) { if( p->to == fa ) continue; dfs2(p->to, x); for(int i=1;i<=hcnt;i++) g[i] = f[x][i]; for(int i=1;i<=hcnt;i++) if( g[i] ) { for(int j=0;j<trans[i].size();j++) { int p1 = trans[i][j].fi, q1 = trans[i][j].se; f[x][q1] = (f[x][q1] + 1LL*g[i]*f[p->to][p1]%MOD)%MOD; } } } } set<int>st; int NS, NT; void get_rand() { srand(20041112^NS^NT); for(int i=1;i<=NT;i++) r[i] = ((ull(rand()) << 16 | rand()) << 16 | rand()) << 16 | rand(); } int main() { scanf("%d", &NS); for(int i=1;i<NS;i++) { int u, v; scanf("%d%d", &u, &v); S.addedge(u, v); } scanf("%d", &NT); for(int i=1;i<NT;i++) { int u, v; scanf("%d%d", &u, &v); T.addedge(u, v); } get_rand(); for(int i=1;i<=NT;i++) dfs1(i, 0), st.insert(id(h[i])); int ans = 0; dfs2(1, 0); for(int i=1;i<=NS;i++) repS(st) ans = (ans + f[i][*it]) % MOD; printf("%d\n", ans); }
这大概是我用 define 等技巧用的最多的一次。
话说本题还能够扩展成屡次询问 T,由于本质不一样的无标号无根树在 n <= 12 的时候其实并很少。(今年牛客第 4 场多校赛好像考了这玩意儿?)