题目大意:
给定一棵树,这两棵树肯定是同构的。
问你,第一棵树的每个节点,可以对应第二个树的那个节点。 显然对应方法不唯一,SPJ来检测结果正确。
方法:
首先找树的重心, 树的重心最多2个。
一个重心的情况很多,两个重心的情况如图:
有人说这个图太对称了……
那给个不对称的。
这个图很重要……涉及到一些奇怪的情况
求树的重心,是一个简单的tree dp
f[i]表示i如果为根(为整棵树的根的情况下),所有儿子节点的size的max。
s[i]表示在做DP中,i的所有儿子的节点总数。
f[i] = max(s[j], n-sum(s[j])-1) 其中j为i的儿子。
然后找f[]数组中元素的最小值,就是树的重心了。
如果第一棵树的重心为A,B。 第二颗数的重心是C,D
那么就检查以A为根,以C为根的两棵树是否同构,再检查(A,D),(B,C),(B,D)是否同构。同构则相对着输出即可。
题解这里判断同构的是给树进行哈希。给的方案是:
(我理解的……)
照抄题解“
这里提供一种方法:首先求解树的中点,然后将中点作为根。只有一个结点的子树哈希值为 1。选一个比较大的质数P和一个特别大的质数Q。对于每一颗树,把它的所有子树的哈希 值排序。然后hash=sum(Pi∗hash[i])%Q,就能算出来总体的哈希值。有两个中点的树两个
中点都试一下。为了保险可以检查下哈希值有没有重的。
”
叶子节点的哈希值为1,假设要求K节点的哈希值。
K有儿子节点A,B,C。并且A<=B<=C(排序好了)
//求哈希值的方法!!!!
hash[k] = (P^1*hash[A])+ (P^2*hash[B])+(P^3*hash[C]) % Q;
hash=sum(Pi∗hash[i])%Q;
P,Q为素数。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
const ll pp[]={435617,999983,327827,674249,986191};
const ll mod=1e9+7;
int n;
ll cnt;
map<string,ll> mapp;
string s1[100010],s2[100010];
vector<ll> g1[100010],g2[100010];
ll Pow(ll a,ll b,ll M)
{
ll ans=1;
a=a%M;
while(b)
{
if(b&1)
ans=ans*a%M;
a=a*a%M;
b/=2;
}
return ans;
}
ll get(char a[])
{
string x="";
for(int i=0;a[i]!=0;x+=a[i++]);
//cout<<x;
if(mapp.find(x)==mapp.end())
{
mapp[x]=++cnt;
}
else
return mapp[x];
return cnt;
}
void init()
{
cnt=0;
mapp.clear();
for(int i=0;i<=n;i++)
{
g1[i].clear();
g2[i].clear();
}
char a[102],b[102];
for(int i=1;i<n;i++)
{
scanf("%s%s",a,b);
ll A=get(a);
s1[A]=a;
ll B=get(b);
s1[B]=b;
g1[A].push_back(B);
g1[B].push_back(A);
// printf("%lld %lld\n",A,B);
}
for(int i=1;i<n;i++)
{
scanf("%s%s",a,b);
ll A=get(a);
s2[A]=a;
ll B=get(b);
s2[B]=b;
g2[A].push_back(B);
g2[B].push_back(A);
//printf("%lld %lld\n",A,B);
}
}
ll dp(int now,vector<ll> g[],ll f[],int father)
{
ll sum=0;
f[now]=0;
if(g[now].size()==0)
{
return 1;
}
for(int i=0;i<g[now].size();i++)
{
int v=g[now][i];
if(v!=father)
{
ll t=dp(v,g,f,now);
sum+=t;
f[now]=max(f[now],t);
}
}
if(now!=father)
f[now]=max(f[now],n-sum-1);
return sum+1;
}
ll f1[100010],f2[100010];
ll f3[100010];
vector<ll> zhong1,zhong2;
struct node
{
ll Hash,id;
node(){}
node(ll h,ll i)
{
Hash=h;
id=i;
}
};
bool operator <(node a,node b)
{
return a.Hash<b.Hash;
}
vector<node> tmp[100010];
vector<ll> g3[100010],g4[100010];
ll myhash(int now,vector<ll> g[],ll f[],int fa,vector<ll> h[])
{
//cout<<"Dsdsd";
if(g[now].size()==1&&g[now][0]==fa)
{
return f[now]=1;
}
ll hash_=0;
tmp[now].clear();
for(int i=0;i<g[now].size();i++)
{
int v=g[now][i];
if(v!=fa)
{
ll t=myhash(v,g,f,now,h);
tmp[now].push_back(node(t,v));
}
}
sort(tmp[now].begin(),tmp[now].end());
for(int i=0;i<tmp[now].size();i++)
{
h[now].push_back(tmp[now][i].id);
hash_=(hash_+(tmp[now][i].Hash*Pow(pp[i%5],i+1,mod)))%mod;
}
f[now]=hash_;
return f[now];
}
void print(int a,int b)
{
// printf("fsfsf");
printf("%s %s\n",s1[a].c_str(),s2[b].c_str());
for(int i=0;i<g3[a].size();i++)
{
print(g3[a][i],g4[b][i]);
}
}
bool check(int a,int b)
{
//cout<<"dsds";
for(int i=0;i<=n;i++)
{
g3[i].clear();
g4[i].clear();
}
myhash(a,g1,f1,0,g3);
myhash(b,g2,f2,0,g4);
if(f1[a]==f2[b])
{
print(a,b);
return true;
}
return false;
}
void solve()
{
/*
第一步,求树的重心
*/
dp(1,g1,f1,1);
dp(1,g2,f2,1);
// for(int i=1;i<=n;i++)
// {
// printf("f2[%d]==%d\n",i,f2[i]);
// }
zhong1.clear();
zhong2.clear();
memmove(f3,f1,sizeof(f3));
sort(f3+1,f3+1+n);
ll tmp=f3[1];
for(int i=1;i<=n;i++)
{
// cout<<f1[i]<<tmp<<f2[i]<<endl;
if(f1[i]==tmp)
zhong1.push_back(i);
if(f2[i]==tmp)
zhong2.push_back(i);
}
for(int i=0;i<zhong1.size();i++)
for(int j=0;j<zhong2.size();j++)
if(check(zhong1[i],zhong2[j]))
return;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
init();//存树
solve();
}
return 0;
}
我上面图上的那个情况,按照我理解的题解的做法,不管你素数取多少,都会重复。导致不管选哪一对重心,都会导致哈希值相同……
然后我给哈希函数改一下,排序好的A,B,C.... 并不都用P这一个素数,每5个数字循环一次,用5个素数来
//最稳定!!(保证不要重复)
hash[k] = (P[1]^1*hash[A])+ (P[2]^2*hash[B])+(P[3]^3*hash[C]) + ....+ [P[I%5+1]^I * hash[?]] % Q
这样…… 然后就AC了。