题目大意
给你一棵
n
个点的基环树,用
答案对
109+7
取模。
给定的基环树是有向的,即环上点连成一个正常的简单环,非环上的点按拓扑序从没有入度的开始向里面连边。
A
和
∙ ∀directed edge (x,y)∈E,(M(x),M(y))∈E
3≤n≤105,1≤m≤109
题目分析
这是一道集合很多套路的好题,虽然每一步都基本上是套路,但是这么多套路一步步串起来,还是让人有点不知道如何下手。
考虑如果题目给的是一棵有根树怎么做。
显然,对于有根树而言,方案出现本质相同的话一定是一个点的同构子树记重了。
计算点
x
的答案时我们先把它所有的儿子按子树哈希值归类,不同类的显然可以乘法原理。
对于同类的,假设有
∑a=1x(xa)(tot−1a−1)=(tot+x−1tot)
这个 x 我只能够算出模
Lucas定理!
(tot+x−1tot)=((tot+x−1) mod Ptot)⎛⎝⌊tot+x−1P⌋⌊totP⌋⎞⎠
注意到 ⌊totP⌋=0 ,我们只需要计算 ((tot+x−1) mod Ptot) 。
我们可以 O(tot) 地算出这个组合数,这样总时间复杂度是 O(n) 的。
现在我们已经可以计算出有根树的染色方案了,考虑如何把它弄到环上。
显然题目模型可以转化为一个长度为 cnt 的简单环,每个点有种类 type (即有根树的哈希值)以及染色方案 x ,求本质不同的染色方案数。
我们使用Burnside引理来做这题。有向环的同构只需要考虑旋转,而这里并不是所有的旋转操作都是合法的。我们的旋转操作必须要满足转了之后对应点的哈希值没有变化。
假如我们是顺时针旋转
显然我们可以通过KMP求出这个最短的周期,然后所有周期都是这个最短周期的倍数。
假如旋转 i 步是合法的话,它的不动点个数就是
我们预处理一下这个环前缀积,就可以很快计算出来。
然后我们的问题就解决了,时间复杂度 O(nlogn) 。
代码实现
#include <algorithm>
#include <iostream>
#include <cassert>
#include <cstdio>
#include <cctype>
#include <vector>
#include <queue>
using namespace std;
typedef vector<int>::iterator itr;
int read()
{
int x=0,f=1;
char ch=getchar();
while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int MOD=1000000007;
const int P=67;
const int N=100050;
int trs[N],deg[N],mult[N],fact[N],invf[N],h[N],size[N],POW[N],list[N],nxt[N],f[N];
int T,n,m,cnt,rt,ans,period;
vector<int> son[N];
queue<int> q;
int quick_power(int x,int y)
{
int ret=1;
for (;y;y>>=1,x=1ll*x*x%MOD) if (y&1) ret=1ll*ret*x%MOD;
return ret;
}
int C(int n,int m)
{
assert(n>=m);
int ret=invf[m];
for (int i=n-m+1;i<=n;++i) ret=1ll*ret*i%MOD;
return ret;
}
bool cmp(int x,int y){return h[x]<h[y];}
int gcd(int x,int y){return y?gcd(y,x%y):x;}
void pre()
{
fact[0]=1;
for (int i=1;i<=n;++i) fact[i]=1ll*fact[i-1]*i%MOD;
invf[n]=quick_power(fact[n],MOD-2);
for (int i=n;i>=1;--i) invf[i-1]=1ll*invf[i]*i%MOD;
POW[0]=1;
for (int i=1;i<=n;++i) POW[i]=1ll*POW[i-1]*P%MOD;
}
void dp()
{
for (int x=1;x<=n;++x) ++deg[trs[x]],size[x]=h[x]=0;
for (int x=1;x<=n;++x) if (!deg[x]) q.push(x);
for (int x,y;!q.empty();)
{
++size[x=q.front()],q.pop(),sort(son[x].begin(),son[x].end(),cmp);
int siz=0;
f[x]=m;
for (itr it=son[x].begin(),it_;it!=son[x].end();)
{
int tot=0;
for (it_=it;it!=son[x].end()&&h[*it]==h[*it_];++it) ++tot,y=*it,(h[x]+=1ll*h[y]*POW[siz<<1]%MOD)%=MOD,siz+=size[y];
f[x]=1ll*f[x]*C((f[*it_]+tot-1)%MOD,tot)%MOD;
}
h[x]=(1ll*h[x]*P+1+2ll*POW[size[x]<<1|1])%MOD;
son[y=trs[x]].push_back(x),size[y]+=size[x];
if (!--deg[y]) q.push(y);
}
for (int x=1,y;x<=n;++x)
if (deg[x])
{
++size[x],sort(son[x].begin(),son[x].end(),cmp);
int siz=0;
f[x]=m;
for (itr it=son[x].begin(),it_;it!=son[x].end();)
{
int tot=0;
for (it_=it;it!=son[x].end()&&h[*it]==h[*it_];++it) ++tot,y=*it,(h[x]+=1ll*h[y]*POW[siz<<1]%MOD)%=MOD,siz+=size[y];
f[x]=1ll*f[x]*C((f[*it_]+tot-1)%MOD,tot)%MOD;
}
h[x]=(1ll*h[x]*P+1+2ll*POW[size[x]<<1|1])%MOD;
}
rt=0;
for (int x=1;x<=n&&!rt;++x) if (deg[x]) rt=x;
cnt=0;
for (int x=rt;x!=rt||deg[rt];deg[x]=0,x=trs[x]) list[cnt++]=x;
int ptr=0;
for (int i=1;i<cnt;++i)
{
for (;ptr&&h[list[i]]!=h[list[ptr]];ptr=nxt[ptr-1]);
nxt[i]=ptr+=h[list[i]]==h[list[ptr]];
}
for (int i=0;i<cnt;++i) mult[i]=1ll*(i?mult[i-1]:1)*f[list[i]]%MOD;
if (cnt-nxt[cnt-1]<=nxt[cnt-1]) period=cnt-nxt[cnt-1];
else period=cnt;
int tot=0;ans=0;
for (int i=0,l;i<cnt;++i)
if (!((l=gcd(i,cnt))%period)) ++tot,(ans+=mult[l-1])%=MOD;
ans=1ll*ans*quick_power(tot,MOD-2)%MOD;
}
void clear(){for (int x=1;x<=n;++x) son[x].clear();}
int main()
{
freopen("color.in","r",stdin),freopen("color.out","w",stdout);
for (T=read();T--;printf("%d\n",ans))
{
clear(),n=read(),m=read();
for (int i=1;i<=n;++i) trs[i]=read();
pre(),dp();
}
fclose(stdin),fclose(stdout);
return 0;
}