考虑建出圆方树。显然只有同一个点相连的某些子树同构会产生贡献。以重心为根后(若有两个任取一个即可),就只需要处理子树内部了。
如果子树的根是圆点,其相连的同构子树可以任意交换,方案数乘上同构子树数量的阶乘即可。而若是方点,注意到其相邻的圆点在原树中是有序地在一个环上的,要产生同构只能旋转或翻转该环。并且因为一开始我们选择了重心为根,所以对于非重心的方点,将其所在的环旋转显然是无法产生贡献的。所以对于方点的所有孩子按环上顺序存储,其哈希值应以该顺序计算,正反取较小的,算贡献时对非重心点只考虑翻转,重心特判一下。判同构当然采取哈希。
调了一年发现只有点双写错了。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> #include<vector> using namespace std; #define ll long long #define N 2010 #define P 1000000003 #define M 10010 #define ull unsigned long long #define p1 509 #define p2 923 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,m,p[N],t,dfn[N],low[N],stk[N],top,cnt,tot,ans=1; vector<int> BCC[N]; struct data{int to,nxt; }edge[M]; void addedge(int x,int y){t++;edge[t].to=y,edge[t].nxt=p[x],p[x]=t;} void tarjan(int k) { dfn[k]=low[k]=++cnt; stk[++top]=k; for (int i=p[k];i;i=edge[i].nxt) if (dfn[edge[i].to]) low[k]=min(low[k],dfn[edge[i].to]); else { tarjan(edge[i].to); low[k]=min(low[k],low[edge[i].to]); if (low[edge[i].to]>=dfn[k]) { tot++; while (stk[top]!=edge[i].to) BCC[tot].push_back(stk[top]),top--; BCC[tot].push_back(edge[i].to);top--; BCC[tot].push_back(k); } } } namespace blocktree { int p[N],t,size[N]; ull hash[N],a[N]; struct data{int to,nxt;}edge[M]; void addedge(int x,int y) { //cout<<x<<' '<<y<<endl; t++;edge[t].to=y,edge[t].nxt=p[x],p[x]=t; t++;edge[t].to=x,edge[t].nxt=p[y],p[y]=t; } void make(int k,int from) { size[k]=1; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) { make(edge[i].to,k); size[k]+=size[edge[i].to]; } } int findroot(int k,int from,int s) { int mx=0; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from&&size[edge[i].to]>size[mx]) mx=edge[i].to; if ((size[mx]<<1)>s) return findroot(mx,k,s); else return k; } void dfs(int k,int from) { for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) dfs(edge[i].to,k); if (k<=n) { int cnt=0; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) a[++cnt]=hash[edge[i].to]; sort(a+1,a+cnt+1); for (int i=1;i<=cnt;i++) hash[k]=hash[k]*p1+a[i]; hash[k]=hash[k]*p2+size[k]; for (int i=1;i<=cnt;i++) { int t=i; while (t<cnt&&a[t+1]==a[i]) t++; int fac=1; for (int j=1;j<=t-i+1;j++) fac=1ll*fac*j%P; ans=1ll*ans*fac%P; i=t; } } else if (k!=from) { if (BCC[k-n].size()>2) { int pos; for (int i=0;i<BCC[k-n].size();i++) if (BCC[k-n][i]==from) {pos=i;break;} ull hash1=0,hash2=0;int x=pos,y=pos; for (int i=1;i<BCC[k-n].size();i++) { x--;if (x<0) x+=BCC[k-n].size(); hash1=hash1*p1+hash[BCC[k-n][x]]; y++;if (y==BCC[k-n].size()) y=0; hash2=hash2*p1+hash[BCC[k-n][y]]; } if (hash1==hash2) ans=2ll*ans%P; hash[k]=min(hash1,hash2)*p2+size[k]; } else for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) hash[k]=hash[edge[i].to]*p2+size[k]; } else { ull hash1=0; for (int i=0;i<BCC[k-n].size();i++) hash1=hash1*p1+hash[BCC[k-n][i]]; int cnt=0; for (int i=0;i<BCC[k-n].size();i++) { int x=i,y=i;ull h1=0,h2=0; for (int j=0;j<BCC[k-n].size();j++) { h1=h1*p1+hash[BCC[k-n][x]]; h2=h2*p1+hash[BCC[k-n][y]]; x++;if (x==BCC[k-n].size()) x=0; y--;if (y<0) y+=BCC[k-n].size(); } if (h1==hash1) cnt++; if (h2==hash1) cnt++; } if (BCC[k-n].size()==2) cnt/=2; ans=1ll*ans*cnt%P; } } void solve() { make(1,1); int root=findroot(1,1,size[1]); make(root,root); dfs(root,root); //cout<<root<<endl; //for (int i=1;i<=19;i++) cout<<size[i]<<' '<<hash[i]<<endl; } } int main() { #ifndef ONLINE_JUDGE freopen("bzoj3899.in","r",stdin); freopen("bzoj3899.out","w",stdout); const char LL[]="%I64d "; #else const char LL[]="%lld "; #endif n=read(),m=read(); for (int i=1;i<=m;i++) { int x=read(),y=read(); addedge(x,y),addedge(y,x); } tarjan(1); for (int i=1;i<=tot;i++) for (int j=0;j<BCC[i].size();j++) blocktree::addedge(n+i,BCC[i][j]); blocktree::solve(); cout<<ans; return 0; }