[BJOI2015]树的同构
https://zybuluo.com/ysner/note/1176508
题面
给出各种形态的树,问哪些树互为重构树?
- \(n\leq50\)
解析
\(method\ 1\)
一开始没注意到不论树有没有根,都要以树的重心为根,根的不同可以改变树的形态,如一棵树变成一条链之类。
树的重心的要求是使子树 最大规模 最小
显然使用树哈希。
\[Hash[x]=\sum_{异或和}(Hash[son_{1..k}]+Base1)*(sz[x]+Base2)+deep[x]*Base3
\]
看起来这式子很容易乘爆,我们可以模一个\(2^n\)(自然溢出也是同一原理),以减少对 大小在模数范围以内 的二进制位的影响。
最后再注意一下找完\(root\)后要重新统计子树大小。
但在\(bzoj\)上死都过不了,很想蒯数据
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=105,mod1=498353,mod2=412817,mod=1<<30;
int n,h[N],cnt,m,B1=3,B2=7,B3=11,sz[N],vis1[500000],vis2[500000],dp[N],root;
ll Hash[N];
struct Edge{int to,next;}e[N<<1];
il void add(re int u,re int v){e[++cnt]=(Edge){v,h[u]};h[u]=cnt;}
il void dfs(re int u,re int fa,re int deep)
{
//printf("%d %d %d\n",u,fa,deep);
re ll sum=0;sz[u]=1;
for(re int i=h[u];i+1;i=e[i].next)
{
re int v=e[i].to;
if(v==fa) continue;
dfs(v,u,deep+1);//printf("%d\n",Hash[v]);
sum^=Hash[v];
sz[u]+=sz[v];
}
Hash[u]^=((sum+B1)*(sz[u]+B2)*(deep+B3));
Hash[u]%=mod;
//printf("%lld %d\n",Hash[u],u);
}
il void getroot(re int u,re int fa)
{
sz[u]=1;
for(re int i=h[u];i+1;i=e[i].next)
{
re int v=e[i].to;
if(v==fa) continue;
getroot(v,u);
sz[u]+=sz[v];
dp[u]=max(dp[u],sz[v]);
}
dp[u]=max(dp[u],n-dp[u]);
if(dp[u]<dp[root]) root=u;
else if(dp[u]==dp[root]&&u<root) root=u;
}
int main()
{
m=gi();
fp(o,1,m)
{
memset(h,-1,sizeof(h));cnt=0;memset(Hash,0,sizeof(Hash));memset(dp,0,sizeof(dp));dp[0]=1e9;memset(sz,0,sizeof(sz));
n=gi();root=0;
fp(i,1,n)
{
re int v=gi();
if(v) add(i,v),add(v,i);
}
getroot(1,0);//printf("%d %d\n",o,root);
dfs(root,0,1);//printf("%d %lld\n",o,Hash[1]);
if(vis1[Hash[root]%mod1]&&vis2[Hash[root]%mod2]) printf("%d\n",vis1[Hash[root]%mod1]);
else printf("%d\n",vis1[Hash[root]%mod1]=vis2[Hash[root]%mod2]=o);
}
return 0;
}
\(method\ 2\)
在对\(sum[v]\)进行排序后,
哈希方程变为这样(\(u\)为当前节点,\(v\)为子节点,\(p[i]\)为质数表)
\[sum[u]=\sum sum[v]*p[i](u\in \{v\})
\]
当然当前节点也算单独一颗\(size=1\)的子树,要不然叶节点怎么办。。。
因该式不考虑诸如深度、以该节点为根的子树等因素,我们就要对每个点为根的情况都进行\(Hash\)值计算,最后排序以后比较是否完全相同即可。
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=105;
int n,h[N],cnt,m,p[55];
ll Hash[N][N],dp[N];
struct Edge{int to,next;}e[N<<1];
il void add(re int u,re int v){e[++cnt]=(Edge){v,h[u]};h[u]=cnt;}
il ll gi()
{
re ll x=0,t=1;
re char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
il void wri(re int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) wri(x/10);
putchar(x%10+'0');
}
il void dfs(re int u,re int fa)
{
re ll top=0,s[55];s[++top]=1;
for(re int i=h[u];i+1;i=e[i].next)
{
re int v=e[i].to;
if(v==fa) continue;
dfs(v,u);
s[++top]=dp[v];
}
dp[u]=0;sort(s+1,s+1+top);
fp(i,1,top) dp[u]+=s[i]*p[i];
}
il void Pre()
{
re int tot=0;
fp(i,41,300)
{
re int flag=1;
fp(j,2,sqrt(i)) if(i%j==0) {flag=0;break;}
if(flag) p[++tot]=i;
if(tot>50) break;
}
}
int main()
{
Pre();
m=gi();
fp(o,1,m)
{
memset(h,-1,sizeof(h));cnt=0;memset(dp,0,sizeof(dp));
n=gi();
fp(i,1,n)
{
re int v=gi();
if(v) add(i,v),add(v,i);
}
fp(i,1,n) dfs(i,0),Hash[o][i]=dp[i];
sort(Hash[o]+1,Hash[o]+1+n);
//fp(i,1,n) printf("%lld ",Hash[o][i]);puts("");
fp(i,1,o)
{
re int flag=1;
fp(j,1,n) if(Hash[o][j]!=Hash[i][j])
{
//printf("%d %d %lld\n",i,j,Hash[i][j]);
flag=0;break;
}
if(flag) {printf("%d\n",i);break;}
}
}
return 0;
}