• @codeforces



    @description@

    给定两棵树 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 中的边。

    Output
    输出一行一个整数,表示连通子图数量 mod 10^9 + 7。

    Examples
    Input
    5
    1 2
    2 3
    3 4
    4 5
    3
    1 2
    2 3
    Output
    3

    Input
    3
    2 3
    3 1
    3
    1 2
    1 3
    Output
    1

    @solution@

    将 S 转为有根树。考虑在连通子图的最高点统计该连通子图的贡献。
    枚举 T 中每一个点作为根(注意要使用树哈希判断是否之前枚举过同构的情况),然后计算此时的贡献。

    假设 x 为连通子图的最高点。此时因为 T 的根也已经确定,那么父子顺序不会改变。
    相当于从 x 中选择一些子树去匹配 T 的根下面的所有子树。这就非常有 dp 的样子了。
    记 dp(x, s) 表示以 x 为根,匹配 T 中以 s 为根的子树的方案数。转移的时候枚举儿子是对应 T 中的哪一个儿子(或者是空,即一个都不对应)。
    但是这个转移太慢了,我们需要同时枚举 x 下的所有子树的对应情况。

    考虑优化,即每次加入 x 下的一棵子树,更新状态。
    那么 s 的定义就拓宽了:s 可以是 T 中某一结点 p + p 的某些子树。
    此时只需要再枚举 x 的这棵子树选成 s1 这一状态,通过某种方法将 s 与 s1 变成新的 s' 就可以实现转移了。

    看上去复杂度不太对?感觉这个肯定会 TLE?
    注意 s 的定义,可以等价于选一个点 p,然后删去 p 的某些往外延伸的子树(这些子树包括父亲那个方向的)。
    那么 s 的范围应该是 O(2^m)。

    转移的时候,因为同时是在 T 的某个结点 p 之下加入某一子树,那么可能性只有 p 的儿子个数那么多。
    即对于每个状态,只会有 O(m) 种转移。
    处理出这 O(m) 种可能的转移,即可做到 O(nm2^m) 的 dp 复杂度,足以通过本题(而且还卡不满,因为有很多同构的子树)。
    用树哈希 + map 可以将一个子树映射成一个整数 id。

    我们可以先对 T 的所有结点为根进行 dfs 处理合法的状态与转移,再对 S 进行 dp。

    @accepted code@

    #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
    ", ans);
    }
    

    @detail@

    这大概是我用 define 等技巧用的最多的一次。

    话说本题还可以扩展成多次询问 T,因为本质不同的无标号无根树在 n <= 12 的时候其实并不多。(今年牛客第 4 场多校赛好像考了这玩意儿?)

  • 相关阅读:
    急招.NET系列职位
    程序员成长的三个方法
    xwebkitspeech
    张小龙的产品
    浅析商业银行“业务连续性管理体系”的构建
    Sonar for dotNet
    Moles测试Contrustor时候遇到的一个问题
    EntityFramework 用Moles的mock
    Accessor中Generic的元素是internal/private的会导致转换失败的异常
    Android自用Intent 介绍
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11807498.html
Copyright © 2020-2023  润新知