一、题目描述:
给你一颗 $n$ 个点的有根树 $S$,你需要构造一颗 $n$ 个节点的有根树 $T$,
使得 $T$ 的 $n$ 颗子树中不与 $S$ 的任意一颗子树同构的数量最大。
注意,这里是有根树,旋转树之后的同构不算同构。输出 $T$ 的所有边。
数据范围:$1\le n\le 1\times 10^6$。
二、解题思路:
树同构,本能地想到树哈希。而树哈希推荐 $OI\ Wiki$ 上的写法,不容易被卡。
容易发现,若一棵子树不与 $S$ 中的任意一颗子树同构,那么他的父亲子树也满足条件。
所以我们只需要找到最小的一颗子树使得它不与 $S$ 同构,然后往上不断加链就行。
发现这颗最小的子树一定会很小,因为 $S$ 一共只有 $n$ 颗子树。
所以我们直接从小到大枚举子树就行。设这颗子树的大小为 $V$,则时间复杂度 $O(n\times V)$ 。
那么这颗子树最大有多大呢?考虑 $n$ 个点的子树有多少种不同的形态。
注意到一颗多叉树唯一对应一颗二叉树(分左右儿子),而一颗二叉树也唯一对应一颗多叉树。
又注意到一颗二叉树唯一对应一颗完全二叉树,一颗完全二叉树唯一对应一颗二叉树。
所以就是求一颗 $n$ 个节点的完全二叉树有多少种形态。而这是卡特兰数经典题。
$Cat_{14}=2674440\ge 1\times 10^6$,所以树的大小最大为 $14$。时间复杂度 $O(14n)$。
考虑怎么不重不漏的枚举树的形态。暴搜枚举每一个点的父亲节点。
但是这样很明显会枚举到很多同构的树,时间复杂度变为 $14!\times 14$。难以通过本题。
其实只需要做一个小剪枝就可以了,就是枚举的父亲节点顺序单调不降。
想一下就想明白了,就像那个什么最小表示法一样。于是时间复杂度 $O(14n)$。
三、完整代码:
1 #include<bits/stdc++.h> 2 #define V e[i].v 3 #define N 1000010 4 #define rep(i,l,r) for(int i=l;i<=r;i++) 5 using namespace std; 6 map <int,bool> mp; 7 mt19937 m(time(0)); 8 int n,mask,ha[N],fa[N]; 9 struct EDGE{ 10 int v,nxt; 11 }e[N<<1]; 12 int head[N],cnt; 13 void add(int u,int v){ 14 e[++cnt].v=v; 15 e[cnt].nxt=head[u]; 16 head[u]=cnt; 17 } 18 int shift(int val){ 19 val^=mask; 20 val^=val<<13; 21 val^=val>>7; 22 val^=val<<17; 23 val^=mask; 24 return val; 25 } 26 void dfs(int u,int ff){ 27 ha[u]=1; for(int i=head[u];i!=-1;i=e[i].nxt) 28 if(V!=ff) dfs(V,u),ha[u]+=ha[V]; 29 ha[u]=shift(ha[u]); 30 } 31 void dfs2(int step,int lim,int num){ 32 if(step>num){ 33 rep(i,1,num) head[i]=-1; cnt=0; 34 rep(i,2,num) add(fa[i],i); dfs(1,0); 35 if(!mp[ha[1]]){ 36 int k=n-num; 37 rep(i,1,k) cout<<i<<" "<<i+1<<'\n'; 38 rep(i,2,num) cout<<i+k<<" "<<fa[i]+k<<'\n'; 39 exit(0); 40 } return ; 41 } 42 rep(i,lim,step-1) fa[step]=i,dfs2(step+1,i,num); 43 } 44 int main(){ 45 ios::sync_with_stdio(false); 46 cin.tie(0);cout.tie(0); 47 cin>>n; mask=m(); 48 rep(i,1,n) head[i]=-1; 49 rep(i,1,n-1){ 50 int u,v; cin>>u>>v; 51 add(u,v); add(v,u); 52 } 53 dfs(1,0); rep(i,1,n) mp[ha[i]]=1; 54 rep(i,1,n) dfs2(2,1,i); 55 rep(i,2,n) cout<<i-1<<" "<<i<<'\n'; 56 return 0; 57 }
四、写题心得:
好,第二次写树哈希,收获经验如下:
$1、shift\ 还是要多练练,背下来。=> Exp++!$
$2、这个随机数就很难被卡,以后取模也可以使用随机模数=> Exp++!$