题目描述
Description
“我不适合你,你有更好的未来。”
当小A当上主持的那一天,他接受记者采访的时候,回忆起了10年前小N离开自己的那句话。
小A出家,其实是因为他已经勘探到了宇宙的奥秘,他希望遁入佛门,通过自己的可修,创造出超越宇宙的秘法,从而突破宇宙的束缚,达到大无畏之境界。
好吧,小A最近碰到了一个挺恶心的问题。
首先,先介绍仙人掌树。仙人掌树是一张无向图,但是每个节点最多只会在一个环里面,而且这张图的环全部都是简单环,即A->B->C->A这种。
比如下图就是一颗仙人掌树。
好的,知道了仙人掌树之后,我们现在要计算一个东西。
我们现在已经知道了一个N个节点的仙人掌树,称作为原图。接下来,我们要用1-N的一个排列A[1]-A[N]去变换这棵树,具体的,如果原图中有一条边i-j,那么变换出来的图中必须有一条A[i]-A[j]的边。同样的,如果变换出来的图中有一条A[i]-A[j]的边,那么原图中必有一条i-j的边。(简单而言就是点重新编号)
小A为了超脱宇宙的束缚,必须要知道,有多少种排列,可以使得变换出来的新图和原图是一模一样的,具体的,原图中如果存在一条i-j的边,新图也存在一条i-j的边,新图中存在一条i-j的边,原图中也存在i-j的边。
方案数目答案mod 1000000003。
Input
第一行有两个正整数,N和M,节点个数和边的个数。
接下来M行,每行有2个正整数S,T,表示一条原图的无向边。数据保证没有重边。
Output
一行一个正整数表示方案书目。
Sample Input
5 5
1 2
2 3
3 4
4 5
1 5
Sample Output
10
解释:
所有的答案包括(i,(i+1) % 5 + 1,(i+2) % 5 + 1,(i+3) % 5 + 1,(i+4) % 5 + 1)和(i,(i+4) % 5 + 1,(i+3) % 5 + 1,(i+2) % 5 + 1,(i+1) % 5 + 1)这两种类型。每种类型的i可以是12345,所以答案是2*5=10。
Data Constraint
点双联通分量
类似于边双,就是一个删掉其中任意一个点仍能连通的点集
然后就需要求割边
割边的定义是删去点t后能使以son为根的子树分离出去的边t–>s
(其实就是求割顶但我不屑于这样理解)
求法很简单,当dfn[t]<=low[son]时,t–>son这条边就是割边
显然如果dfn[t]>low[son]时,son以及其子树中一定有一个点可以走到t的祖先(返祖边)
那么当dfn[t]<=low[son]时,这条边就是割边了
类似于求边双,以son为结尾的点集可以构成一个点双
如果当前点为根,那么只有当根在Tarjan树上有≥2个儿子时,根连出去的每一条边都是割边
因为如果有两个儿子,那么这两个儿子之间一定没有边相连(否则就是一个儿子了)
所以删掉根可以把原图分成两个块
感性理解
至于dfn和low的求法,其实和边双一样
但是为了防止子树之间相互影响,要在low求出后再赋值
我原来写的是假的Tarjan
code(点双)
void Tarjan(int fa,int t)
{
int i,Low;
dfn[t]=++j;
low[t]=j;
Low=j;
bz[t]=1;
for (i=ls[t]; i; i=a[i][1])
if (a[i][0]!=fa)
{
if (!dfn[a[i][0]])
{
Tarjan(t,a[i][0]);
l+=(t==1);
Low=min(Low,low[a[i][0]]);
}
else
if (bz[a[i][0]])
Low=min(Low,low[a[i][0]]);
}
bz[t]=0;
low[t]=Low;
}
void find(int t)
{
if (d[l][0]==t)
{
NEW(d[l][0],d[l][1]);
l--;
}
else
{
N++;
while (d[l][0]!=t)
NEW(d[l--][0],N);
NEW(d[l--][0],N);
}
}
void tarjan(int fa,int t)
{
int i;
bz[t]=1;
Bz[t]=1;
for (i=ls[t]; i; i=a[i][1])
if (a[i][0]!=fa)
{
if (!bz[a[i][0]])
{
l++;
d[l][0]=t;
d[l][1]=a[i][0];
tarjan(t,a[i][0]);
if (dfn[t]<=low[a[i][0]] && (t>1 || BZ))
find(t);
}
else
if (Bz[a[i][0]])
{
l++;
d[l][0]=t;
d[l][1]=a[i][0];
}
}
Bz[t]=0;
}
圆方树
因为这道题给出的是一颗仙人掌,所以不能直接求树的同构方案
所以考虑把仙人掌转成一颗树
如图所示,只需要在每个点双中建一个点(称之为方点),然后点双里的每个点(称之为圆点)都向它连边
新建出来的树就叫圆方树
圆方树有一些奇♂妙的性♂质,最基本的就是每个圆点代表一个点,每个方点代表一个点双(在本题中就是环)
然后就可以求仙人掌的同构了
求同构
先假设节点1映射自己,求以某个点为根时的同构方案数tot[t],之后向上合并
对于一个圆点,因为没有限制,所以方案数就是所有子树方案数之积*所有(一种子树的出现次数的阶乘)之积
显然
因为没有限制,所以每个子树都有tot[son]种方案,一共就是Πtot[son]种
对于相同形态的子树,可以互相映射,所以有Π(一种子树的个数!)种
结果就是两者之积
(不用考虑自己是因为当前点已经确定了映射的位置)
因为方点代表的是一个环,所以不能随便重构
显然环可以翻转,所以每次以当前点为中心判断对称,如果可以翻转答案就*2
至于为什么要以当前点为中心,是因为每个点在处理时都已经确定了儿子的映射情况,而当前点的父亲已经被考虑过了,所以当前点不能再与其他点映射
还有一个问题,就是如何判断子树的形态相同
考虑哈希,具体实现可以自由选择,但要分圆点和方点来计算
圆点:因为不考虑顺序,所以把子树的哈希值乘起来
方点:因为考虑顺序,所以要以当前点按顺/逆时针分别求出两个不同的值,然后相乘(或者取min,总之随便搞)
处理时可以先在建圆方树时按顺序连边,然后把得出的序列对齐,最后计算两个方向的值反正随便搞能AC就行
实现过程中为了减少重复的可能,要尽量把子树的特征记录到哈希值里
子树深度:每次求完哈希值后取平方
儿子个数:乘以儿子数的阶乘
方点的顺序:每次平方后再加
最后加上双哈希好像不加也可以
code
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(a,b) (a<b?a:b)
#define mod 1000000003
#define Mod 998244353
using namespace std;
int a[10001][2];
int A[10001][2];
int ls[2001];
int Ls[2001];
int dfn[1001];
int low[1001];
int C[2001][2];
bool bz[1001];
bool Bz[1001];
int d[10001][2];
long long tot[2001];
long long hash[2001][2];
long long jc[1001];
int N,n,m,i,j,k,K,l,len,Len;
long long ans;
bool BZ,_bz;
void swap(int &a,int &b) {int c=a;a=b;b=c;}
void qsort(int l,int r)
{
int i,j,mid,Mid;
i=l;
j=r;
mid=C[(l+r)/2][0];
Mid=C[(l+r)/2][1];
while (i<=j)
{
while (C[i][0]<mid || C[i][0]==mid && C[i][1]<Mid) i++;
while (C[j][0]>mid || C[j][1]==mid && C[j][1]>Mid) j--;
if (i<=j)
{
swap(C[i][0],C[j][0]);
swap(C[i][1],C[j][1]);
i++,j--;
}
}
if (l<j) qsort(l,j);
if (i<r) qsort(i,r);
return;
}
void New(int x,int y)
{
len++;
a[len][0]=y;
a[len][1]=ls[x];
ls[x]=len;
}
void _new(int x,int y)
{
Len++;
A[Len][0]=y;
A[Len][1]=Ls[x];
Ls[x]=Len;
}
void NEW(int x,int y) {_new(x,y),_new(y,x);}
void Tarjan(int fa,int t)
{
int i,Low;
dfn[t]=++j;
low[t]=j;
Low=j;
bz[t]=1;
for (i=ls[t]; i; i=a[i][1])
if (a[i][0]!=fa)
{
if (!dfn[a[i][0]])
{
Tarjan(t,a[i][0]);
l+=(t==1);
Low=min(Low,low[a[i][0]]);
}
else
if (bz[a[i][0]])
Low=min(Low,low[a[i][0]]);
}
bz[t]=0;
low[t]=Low;
}
void find(int t)
{
if (d[l][0]==t)
{
NEW(d[l][0],d[l][1]);
l--;
}
else
{
N++;
while (d[l][0]!=t)
NEW(d[l--][0],N);
NEW(d[l--][0],N);
}
}
void tarjan(int fa,int t)
{
int i;
bz[t]=1;
Bz[t]=1;
for (i=ls[t]; i; i=a[i][1])
if (a[i][0]!=fa)
{
if (!bz[a[i][0]])
{
l++;
d[l][0]=t;
d[l][1]=a[i][0];
tarjan(t,a[i][0]);
if (dfn[t]<=low[a[i][0]] && (t>1 || BZ))
find(t);
}
else
if (Bz[a[i][0]])
{
l++;
d[l][0]=t;
d[l][1]=a[i][0];
}
}
Bz[t]=0;
}
void init()
{
jc[0]=1;
fo(i,1,1000)
jc[i]=jc[i-1]*i%mod;
scanf("%d%d",&n,&m); N=n;
fo(i,1,m)
{
scanf("%d%d",&j,&k);
New(j,k);
New(k,j);
}
j=0;l=0;
Tarjan(0,1);
BZ=(l>1);
l=0;
tarjan(0,1);
if (l) find(1);
memcpy(a,A,sizeof(a));
memcpy(ls,Ls,sizeof(ls));
len=Len;
}
void dfs(int fa,int t)
{
int i,j,k,L,l=0,l2;
bool bz;
tot[t]=1;
hash[t][0]=2;
hash[t][1]=2;
for (i=ls[t]; i; i=a[i][1])
if (a[i][0]!=fa)
dfs(t,a[i][0]);
if (t>n && !fa)
{
l=1;
C[1][0]=-1;
}
for (i=ls[t]; i; i=a[i][1])
if (a[i][0]!=fa)
{
l++;
C[l][0]=hash[a[i][0]][0]%mod;
C[l][1]=hash[a[i][0]][1]%Mod;
tot[t]=tot[t]*tot[a[i][0]]%mod;
}
else
if (t>n)
C[++l][0]=-1;
l2=l;
if (t<=n) //circle
{
qsort(1,l);
j=1;
fo(i,2,l)
if (C[i-1][0]==C[i][0] && C[i-1][1]==C[i][1])
j++;
else
{
tot[t]=tot[t]*jc[j]%mod;
j=1;
}
tot[t]=tot[t]*jc[j]%mod;
fo(i,1,l)
{
hash[t][0]=hash[t][0]*C[i][0]%mod;
hash[t][1]=hash[t][1]*C[i][1]%Mod;
}
}
else //square
{
L=1;
while (C[L][0]!=-1)
{
l++;
C[l][0]=C[L][0];
C[l][1]=C[L][1];
L++;
}
L++;
long long h1=2,h2=2;
fo(i,L,l) h1=(h1*h1+C[i][0])%mod;
fd(i,l,L) h2=(h2*h2+C[i][0])%mod;
hash[t][0]=h1*h2%mod;
h1=2,h2=2;
fo(i,L,l) h1=(h1*h1+C[i][1])%Mod;
fd(i,l,L) h2=(h2*h2+C[i][1])%Mod;
hash[t][1]=h1*h2%Mod;
if (_bz)
{
bz=1;
j=L;k=l;
while (j<k)
{
if (C[j][0]!=C[k][0] || C[j][1]!=C[k][1])
{
bz=0;
break;
}
j++;k--;
}
if (bz) ans=(ans<<1)%mod;
}
}
hash[t][0]=hash[t][0]*hash[t][0]%mod*(jc[l2]+1)%mod;
hash[t][1]=hash[t][1]*hash[t][1]%Mod*(jc[l2]+1)%Mod;
}
void work()
{
ans=1;
_bz=1;
dfs(0,1);
_bz=0;
ans=ans*tot[1]%mod;
k=hash[1][0];
K=hash[1][1];
l=1;
fo(i,2,n)
{
dfs(0,i);
l+=(hash[i][0]==k && hash[i][1]==K);
}
ans=ans*l%mod;
}
int main()
{
init();
work();
printf("%lld\n",ans);
}