Problem
定义仙人掌树为:1)无向图;2)每个点最多在一个简单环中。给定一棵N(≤1000)个点、M条边的仙人掌树(保证没有重边),求这棵仙人掌树的同构数量。
Solution
- 首先,将原仙人掌转化成一棵圆方树。因为对于每个仙人掌,都有且仅有一棵对应的圆方树,故原题可转化为这棵圆方树的同构数量。
- 不能使用普通的树的同构的计数方法。因为对于普通的树,其形态相同的子树均可随意换位;而仙人掌中的环却不能随意转动。
- 考虑另辟蹊径,解决这个问题。
- 首先,对于一个圆点,它的形态相同的子树是可以随意换位的。
- 于是,对于其一个子节点i,扫一遍统计出与其形态相同的子树的数量q,ans*=q!。
- 因为它们形态相同,于是可以任意排列,因此,一个阶乘就可以解决问题。
- 对于一个方点,我们看一看它的子节点是否对称。
- 设a[1..s]为其所有子节点,我们要做的就是判断a[1]==a[s]&&a[2]==a[s-1]&&a[3]==a[s-2]&&…&&a[s/2]==a[s-s/2+1]。如果为真,则表明这个环对称,我们可以让它进行左右翻转,于是ans<<=1。
- 现在要考虑如何判断两棵子树形态是否相同。
- 考虑哈希。我们可以使用
括号序、高度deep子树大小sz计算哈希值。 - 这要如何计算呢?我们可以分类讨论。
- 对于一个圆点,我们按哈希值对其子节点排序。这样就可以保证不会因为两棵子树的子节点顺序不同而误判其形态不同。
- 然后,我们顺着扫一遍,像普通哈希一样,用子节点的哈希值计算。具体参考代码。
- 算完之后,再用这个圆点自己的sz算一波。
- 对于一个方点,就不太好做了。
- 我们不能对其子节点排序,因为我们不能随意转动一个环。
- 可以顺着扫一遍求哈希值,再逆着扫一遍求哈希值,并将两个答案取min。这样就对应它可以左右翻转。
- 然后是换根操作。
- 以点1为根算出ans后,记录点1的哈希值为rt。
- 我们再以点2~点n为根算一遍。记录有q个哈希值相同的,最后ans*=(q+1)。
- 但是这样做,你会得到这个东西:
- 分析一下,对于一个方点,我们并没有绝对的顺序去记录它的子节点,于是可能某些环是被转动了。
- 可以记录一下方点连向其父亲节点的边,然后以这条边开始(按顺时针或逆时针)记录子节点(当然不包括这条边)。
- 这样一来,我们就会从环的根开始,顺时针(或逆时针)记录下整个环;于是相同的环就不会因为记录顺序不同而被误判不同。
Code
#include <cstdio>
#include <algorithm>
#define fi first
#define se second
#define mp make_pair
#define MIN(x,y) x=min(x,y)
#define fo(i,a,b) for(i=a;i<=b;i++)
#define rep(i,x) for(edge *i=x; i; i=i->ne)
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int N=2001;
const ll M=1000000003,p1=71,p2=57,m1=73651,m2=90437;
int i,n,m,num,s,t;
struct edge
{
int v;
edge *ne;
inline edge(int _v,edge *_ne)
{
v=_v; ne=_ne;
}
}*final[N],*fin[N];
inline void link(int x,int y,edge **f)
{
f[x]=new edge(y,f[x]);
f[y]=new edge(x,f[y]);
}
int tim,dfn[N],low[N],fat[N];
bool ring[N];
void tarjan(int x)
{
dfn[x]=low[x]=++tim;
rep(i,final[x])
{
int y=i->v;
if(y!=fat[x])
if(!dfn[y])
{
fat[y]=x; ring[x]=0; tarjan(y);
if(!ring[x]) link(x,y,fin);
}
else
if(dfn[y]<dfn[x])
{
int z=x; num++;
for(;z!=y;z=fat[z]) link(num,z,fin), ring[z]=1;
link(num,y,fin); ring[y]=1;
}
}
}
ll fac[N],sz[N],ans,Ans;
P a[N],hash[N],sum,rt;
bool bz[N];
void getsum(int x)
{
int i; sum=mp(0,0);
fo(i,1,s)
{
sum.fi=(sum.fi*p1+a[i].fi)%m1;
sum.se=(sum.se*p2+a[i].se)%m2;
}
sum.fi=(sum.fi*p1+sz[x])*sz[x]%m1;
sum.se=(sum.se*p2+sz[x])*sz[x]%m2;
}
bool symmetry()
{
int i;
fo(i,1,s>>1) if(a[i]!=a[s-i+1]) return 0;
return 1;
}
void dfs(int x)
{
int y; sz[x]=1; edge *q=NULL;
rep(i,fin[x])
{
y=i->v;
if(y!=fat[x]) fat[y]=x, dfs(y), sz[x]+=sz[y];
else q=i;
}
s=0;
if(q) rep(i,q->ne)
{
y=i->v;
a[++s]=hash[y];
}
for(edge *i=fin[x]; i!=q; i=i->ne)
{
y=i->v;
a[++s]=hash[y];
}
hash[x]=mp(0,0);
int i;
if(x<=n)
{
fo(i,1,s) bz[i]=0;
fo(i,1,s)
if(!bz[i])
{
int q=0,j;
fo(j,i,s) if(a[i]==a[j]) q++, bz[j]=1;
(ans*=fac[q])%=M;
}
sort(a+1,a+s+1);
getsum(x);
hash[x]=sum;
}
else
{
if(symmetry()) (ans<<=1)%=M;
getsum(x); hash[x]=sum;
reverse(a+1,a+s+1);
getsum(x); MIN(hash[x].fi,sum.fi); MIN(hash[x].se,sum.se);
}
}
int main()
{
scanf("%d%d",&n,&m); num=n;
fo(i,1,m)
{
scanf("%d%d",&s,&t);
link(s,t,final);
}
tarjan(2);
ans=fac[0]=1;
fo(i,1,num) fac[i]=fac[i-1]*1ll*i%M;
fat[1]=0;
dfs(1);
Ans=ans; rt=hash[1]; ll q=1;
fo(i,2,n)
{
fat[i]=0; dfs(i);
if(rt==hash[i]) q++;
}
printf("%lld",Ans*q%M);
}