jzoj3336. 【NOI2013模擬】坑帶的樹(圓方樹)
題目描述
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][