测试地址:仙人掌树的同构
题目大意:定义一棵仙人掌树为,每个点最多在一个环中的无向图,且图中的环都是简单环。问有多少种点的置换,使得置换后的图和原图相同。
n≤1000
n
≤
1000
。
做法:本题需要用到圆方树+树上哈希+DP。
首先显然的是,仙人掌同构就等同于圆方树同构。不过这题的仙人掌定义和一般的仙人掌有些不同:是每个点最多在一个环中,而不是每条边。又因为没有重边,所以没有大小为
2
2
的环。通过这个性质我们可以更简单地写出圆方树。为了讨论方便,这里把度数为的方点都省略,直接将它连接的两个点连接。
我们随便选一个圆点作为根,令
f(v)
f
(
v
)
为以点
v
v
为根的子树中,当点确认置换成某一个等价的点时,这棵子树内部有多少种置换。那么:
对于一个圆点,考虑它所有子树的哈希值,如果有
k
k
个子树的哈希值相同,那它们的点之间可以互换,因此方案数乘上。
而对于一个方点,因为环实际上是有顺序的,所以不能像上面一样随便互换。因为环的某一个点已经确定了(该方点的父亲),所以我们只能对这个环做翻转变换,看它和原图是不是同构,如果是,答案就乘上
2
2
<script type="math/tex" id="MathJax-Element-1738">2</script>。
在哈希时也要注意,要把圆点和方点区分,要给它们设置不同的哈希参数。而对于方点的哈希,因为我们说过了,环上的点是有顺序的,不能打乱,所以我们把方点的子树正反哈希两遍,取其中的最小值作为方点的哈希值即可。注意写的时候一定要非常注意环上点的顺序。
那这样是不是就完了呢?还没有。注意到我们上面求出的是,选定的根确定的情况下置换的数目,所以我们需要对每个圆点往下都做一遍树哈希,来看有多少个点和选定的根等价,最后再乘上这个点数才是答案。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const ll mod=1000000003;
const ll P[2]={131,103};
int n,m,first[2010]={0},tfirst[2010]={0},tot=0;
int fa[2010],totpbc,belong[2010];
ll fac[2010],tmp[2010],siz[2010],down[2010],Hash[2010],ans=1;
bool vis[2010]={0};
struct edge
{
int v,next;
}e[5010],t[5010];
void insert(edge *e,int *first,int a,int b)
{
e[++tot].v=b;
e[tot].next=first[a];
first[a]=tot;
}
void build(int v)
{
vis[v]=1;
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=fa[v])
{
if (vis[e[i].v])
{
if (belong[e[i].v]) continue;
belong[e[i].v]=++totpbc;
insert(t,tfirst,e[i].v,totpbc);
insert(t,tfirst,totpbc,e[i].v);
for(int p=v;p!=e[i].v;p=fa[p])
{
belong[p]=totpbc;
insert(t,tfirst,p,totpbc);
insert(t,tfirst,totpbc,p);
}
}
else fa[e[i].v]=v,build(e[i].v);
}
if (!belong[v]) belong[v]=++totpbc;
for(int i=first[v];i;i=e[i].next)
if (e[i].v!=fa[v]&&belong[v]!=belong[e[i].v])
{
insert(t,tfirst,v,e[i].v);
insert(t,tfirst,e[i].v,v);
}
}
void dfs(int v,int fa)
{
int type=(v>n),to=0;
siz[v]=1;
for(int i=tfirst[v];i;i=t[i].next)
{
if (t[i].v!=fa)
{
dfs(t[i].v,v);
siz[v]+=siz[t[i].v];
}
else to=i;
}
tot=0;
if (to)
{
for(int i=t[to].next;i;i=t[i].next)
tmp[++tot]=down[t[i].v];
}
for(int i=tfirst[v];i!=to;i=t[i].next)
tmp[++tot]=down[t[i].v];
if (v<=n)
{
for(int i=1;i<=tot;i++)
vis[i]=0;
for(int i=1;i<=tot;i++)
if (!vis[i])
{
int cnt=0;
for(int j=i;j<=tot;j++)
if (tmp[i]==tmp[j])
{
vis[j]=1;
cnt++;
}
ans=ans*fac[cnt]%mod;
}
sort(tmp+1,tmp+tot+1);
down[v]=0;
for(int i=1;i<=tot;i++)
down[v]=down[v]*P[type]+tmp[i];
down[v]=down[v]*P[type]+siz[v];
down[v]*=siz[v];
}
else
{
bool flag=1;
for(int i=1;i<=tot;i++)
if (tmp[i]!=tmp[tot-i+1])
{
flag=0;
break;
}
if (flag) ans=(ans<<1)%mod;
down[v]=0;
for(int i=1;i<=tot;i++)
down[v]=down[v]*P[type]+tmp[i];
down[v]=down[v]*P[type]+siz[v];
down[v]*=siz[v];
ll now=0;
for(int i=tot;i>=1;i--)
now=now*P[type]+tmp[i];
now=now*P[type]+siz[v];
now*=siz[v];
down[v]=min(down[v],now);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
insert(e,first,a,b);
insert(e,first,b,a);
}
fac[0]=1;
for(ll i=1;i<=(n<<1);i++)
fac[i]=fac[i-1]*i%mod;
tot=0;totpbc=n;
fa[1]=0;
build(1);
dfs(1,0);
ll same=down[1],cnt=1,now=ans;
for(int i=2;i<=n;i++)
{
dfs(i,0);
if (same==down[i]) cnt++;
}
printf("%llu\n",now*cnt%mod);
return 0;
}