一、题目描述:

  给你一颗 $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++!$

posted on 2023-09-14 21:18  trh0630  阅读(25)  评论(0编辑  收藏  举报