bzoj4754[JSOI2016]独特的树叶
这个题....别人写得怎么都....那么短啊?
我怎么....WA了好几次啊....怎么去loj扒了数据才调出来啊?
这个算法...怎么我还是不知道对不对啊
怎么回事啊怎么回事啊怎么回事啊?
请无视上面的梦话
代码太长应该是因为我把两棵树都有的函数通过复制改名实现,如果写成结构体大概能短一半吧
题意
给出两棵树A和B,A有n个点,B有n+1个点,且B是由A的某个点上多连一个点再把节点重新标号得到的.问多连的那个点在B中的编号.有多解时输出最小的.
分析
首先我们发现这是个树同构题目!然后我们只会用哈希做树同构!因此我们猜想一定可以快速求出树B删去某个点之后的哈希值!然后别人就会做了!我不会做!
考虑加一个点之后重心的变化.无根树重心有1到2个,如果有2个那么这两个一定相邻,且连接两个重心的边可以把树分成大小相等的两部分.那么我们分几种情况画画图,发现有这么几种情况:
- 加入节点之前有1个重心,加入后也是1个重心,那么这两个重心必然是同一个点.
- 加入节点之前有1个重心,加入后有2个重心,那么加入后的2个重心中必然有一个是原先的重心.
- 加入节点之前有2个重心,加入后有1个重心,那么加入后的1个重心必然是之前的2个重心当中的1个.
- 不存在加入之前有2个重心,加入后也有2个重心的情况,因为有2个重心要求树的节点个数必须是偶数.
那么不妨找出树A和B的重心,并求出以重心为根时所有子树的哈希值.因为加上的那个叶节点不可能成为重心,所以这个叶节点一定在以重心的某个儿子为根的子树内,而其他的子树必然是同构的,否则就不满足加上的节点只有1个.因此我们尝试把两棵树以重心为根的同构的子树匹配起来,如果最后只剩下两棵子树哈希值不同,就可以递归下去了.因为每次都是选的重心,所以递归深度不超过\(O(logn)\).
这里有两个细节:
- 重心可能有两个,这个时候要把两种可能的匹配方法都试一下,判断是否合法,如果都合法,就都递归下去.
- 向下递归时选出两棵哈希值不同的子树的方案有可能有多种,因为可能有同构的子树,选哪一个都可以.如果是树A的子树同构,选一个向下递归即可,因为在树A中我们要的只是一个树的形态用来和树B比较,选哪一棵无所谓.但是选择树B的不同子树向下递归可以得到不同的答案编号,所以如果树B选到的子树有同构的,每一棵都要递归下去试一试.
然后我们发现这个算法在loj上是20分,只过了两个小点
Life is like this.
原因是递归到树B中这样形态的子树时挂掉了:
1连2,2连3,3连4,5连3,5还连着其他一些点使得5是重心,发现多余的叶节点在1,2,3,4组成的子树内,然后选择1.但是实际上删去节点4才能和树A同构.原因是递归处理下去的时候我们只考虑了分割出的子树,忽略了和分割出的子树之外的点的关系...然后我们加一些特技...在树hash的时候考虑一下访问到的子树之外的点....作为特殊的参数和稀泥扔到树hash里....然后我们发现这样的忽略还会导致我们在有两个重心的时候选的重心产生"看似"合法的非法解,然后我们发现似乎只需要对每个点记录一下递归到这里时之前选取过的重心有多少个与之相邻,我们在两棵树中选取进行匹配的重心需要具有相同的这个参数...然后就能搞过去了....
实际上我也不知道这个算法有没有什么漏洞...有想法的同学欢迎造数据叉掉或者证明/证伪...把树hash叉掉的不算反正大家都写的树hash
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100005;
struct edge{
int to,next;
}lst1[maxn<<1],lst2[maxn<<1];int len1=1,len2=1,first1[maxn],first2[maxn];
void addedge1(int a,int b){
lst1[len1].to=b;lst1[len1].next=first1[a];first1[a]=len1++;
}
void addedge2(int a,int b){
lst2[len2].to=b;lst2[len2].next=first2[a];first2[a]=len2++;
}
int totsz1;
int root1[3];
int sz1[maxn],f1[maxn];bool vis1[maxn];
void get_root1(int x,int p){
sz1[x]=1;f1[x]=0;
for(int pt=first1[x];pt;pt=lst1[pt].next){
if(lst1[pt].to==p||vis1[lst1[pt].to])continue;
get_root1(lst1[pt].to,x);
sz1[x]+=sz1[lst1[pt].to];
if(sz1[lst1[pt].to]>f1[x])f1[x]=sz1[lst1[pt].to];
}
if(totsz1-sz1[x]>f1[x])f1[x]=totsz1-sz1[x];
if(f1[x]<=totsz1/2)root1[++root1[0]]=x;
}
int totsz2;
int root2[3];
int sz2[maxn],f2[maxn];bool vis2[maxn];
void get_root2(int x,int p){
sz2[x]=1;f2[x]=0;
for(int pt=first2[x];pt;pt=lst2[pt].next){
if(lst2[pt].to==p||vis2[lst2[pt].to])continue;
get_root2(lst2[pt].to,x);
sz2[x]+=sz2[lst2[pt].to];
if(sz2[lst2[pt].to]>f2[x])f2[x]=sz2[lst2[pt].to];
}
if(totsz2-sz2[x]>f2[x])f2[x]=totsz2-sz2[x];
if(f2[x]<=totsz2/2)root2[++root2[0]]=x;
}
int ans=0x7f7f7f7f;
typedef unsigned long long ul;
ul H1[maxn],H2[maxn];
ul seq[maxn];
void gethash1(int x,int p){
H1[x]=233;sz1[x]=1;
for(int pt=first1[x];pt;pt=lst1[pt].next){
if(lst1[pt].to==p||vis1[lst1[pt].to])continue;
gethash1(lst1[pt].to,x);sz1[x]+=sz1[lst1[pt].to];
}
int cnt=0;
for(int pt=first1[x];pt;pt=lst1[pt].next){
if(lst1[pt].to==p)continue;
if(vis1[lst1[pt].to])seq[++cnt]=123123;
else seq[++cnt]=H1[lst1[pt].to];
}
sort(seq+1,seq+cnt+1);
for(int i=1;i<=cnt;++i){
H1[x]=(H1[x]*29+seq[i])^((seq[i]<<6)-H1[x])+23336666;
}
}
void gethash2(int x,int p){
H2[x]=233;sz2[x]=1;
for(int pt=first2[x];pt;pt=lst2[pt].next){
if(lst2[pt].to==p||vis2[lst2[pt].to])continue;
gethash2(lst2[pt].to,x);sz2[x]+=sz2[lst2[pt].to];
}
int cnt=0;
for(int pt=first2[x];pt;pt=lst2[pt].next){
if(lst2[pt].to==p)continue;
if(vis2[lst2[pt].to])seq[++cnt]=123123;
else seq[++cnt]=H2[lst2[pt].to];
}
sort(seq+1,seq+cnt+1);
for(int i=1;i<=cnt;++i){
H2[x]=(H2[x]*29+seq[i])^((seq[i]<<6)-H2[x])+23336666;
}
}
int seq1[maxn],cnt1,seq2[maxn],cnt2;
bool cmp1(const int &a,const int &b){
return H1[a]<H1[b];
}
bool cmp2(const int &a,const int &b){
return H2[a]<H2[b];
}
bool isleaf[maxn];
int beside1[maxn],beside2[maxn];
#define GG {for(int i=1;i<=cnt1;++i)beside1[seq1[i]]--;for(int i=1;i<=cnt2;++i)beside2[seq2[i]]--;return;}
void trymatch(int rt1,int rt2){
gethash1(rt1,0);
gethash2(rt2,0);
cnt1=cnt2=0;
for(int pt=first1[rt1];pt;pt=lst1[pt].next){
if(vis1[lst1[pt].to]){
continue;
}
beside1[lst1[pt].to]++;
seq1[++cnt1]=lst1[pt].to;
}
for(int pt=first2[rt2];pt;pt=lst2[pt].next){
if(vis2[lst2[pt].to]){
continue;
}
beside2[lst2[pt].to]++;
seq2[++cnt2]=lst2[pt].to;
}
if(cnt1>cnt2)GG;
if(cnt1<cnt2){
if(cnt1+1<cnt2)GG;
int leaf=0x7f7f7f7f;
for(int i=1;i<=cnt2;++i){
if(isleaf[seq2[i]]&&H2[seq2[i]]==233&&seq2[i]<leaf){
leaf=seq2[i];
}
}
if(leaf==0x7f7f7f7f)GG;
sort(seq1+1,seq1+cnt1+1,cmp1);
sort(seq2+1,seq2+cnt2+1,cmp2);
int pt=1;
for(int i=1;i<=cnt1;++i){
if(seq2[pt]==leaf)++pt;
if(H1[seq1[i]]!=H2[seq2[pt++]])GG;
}
ans=min(ans,leaf);
}else{
sort(seq1+1,seq1+cnt1+1,cmp1);
sort(seq2+1,seq2+cnt2+1,cmp2);
int pos2=0,cntdiff=0,pos1=0;
int pt=1;
for(int i=1;i<=cnt1;++i){
while(pt<cnt2&&H2[seq2[pt]]<H1[seq1[i]]){
pos2=pt;
++pt;
}
if(H2[seq2[pt]]!=H1[seq1[i]]){
pos1=i;
}
else cntdiff++,pt++;
}
if(pt==cnt2)pos2=cnt2;
if(cntdiff==cnt1-1){
ul sign=H2[seq2[pos2]];
totsz1=sz1[seq1[pos1]];totsz2=sz2[seq2[pos2]];
if(totsz1+1!=totsz2)GG;
int old1,old2;
old1=totsz1;old2=totsz2;
int l=1,r=cnt2;
while(H2[seq2[l]]!=sign)l++;
while(H2[seq2[r]]!=sign)r--;
for(int pos=l;pos<=r;++pos){
root1[0]=root2[0]=0;
totsz1=old1;totsz2=old2;
vis1[rt1]=true;vis2[rt2]=true;
get_root1(seq1[pos1],0);get_root2(seq2[pos],0);
int r1[3],r2[3];for(int i=0;i<3;++i)r1[i]=root1[i],r2[i]=root2[i];
for(int i=1;i<=r1[0];++i){
for(int j=1;j<=r2[0];++j){
if(beside1[r1[i]]==beside2[r2[j]])
trymatch(r1[i],r2[j]);
}
}
vis1[rt1]=false;vis2[rt2]=false;
}
}
}
GG;
}
int main(){
int n;scanf("%d",&n);
for(int i=1,a,b;i<n;++i){
scanf("%d%d",&a,&b);addedge1(a,b);addedge1(b,a);
}
for(int i=1,a,b;i<=n;++i){
scanf("%d%d",&a,&b);addedge2(a,b);addedge2(b,a);
}
for(int i=1;i<=n+1;++i){
int cnt=0;
for(int pt=first2[i];pt;pt=lst2[pt].next){
++cnt;
}
if(cnt==1)isleaf[i]=true;
}
totsz1=n;totsz2=n+1;root1[0]=root2[0]=0;
get_root1(1,0);get_root2(1,0);
int r1[3],r2[3];for(int i=0;i<3;++i)r1[i]=root1[i],r2[i]=root2[i];
for(int i=1;i<=r1[0];++i){
for(int j=1;j<=r2[0];++j){
trymatch(r1[i],r2[j]);
}
}
printf("%d\n",ans);
return 0;
}