标签:operator link inline http 乘法 利用 include read next
Link
如果我们\(i\rightarrow p_i\)建边,那么最后会得到一些有向环。
对于上述的每一个环,如果我们\(i\rightarrow a_i\)建边,那么此时\(a_i\)为\(i\)在顺时针方向下的第\(1\operatorname{or}2\)个点,也就是\(a_i=p_i\vee a_i=p_{p_i}\)。
那么此时有四种情况:
若\(a_i=p_i\),那么这个环不变。
若\(a_i=p_{p_i}\),那我们对环长的奇偶性分开讨论。
若环长为奇数,那么这个环变成元素相同的另一个环。
若环长为偶数,那么这个环会被拆成两个环。
否则部分\(a_i=p_i\),部分\(a_i=p_{p_i}\),那么这个环会被拆成一个环和若干个指向环的链构成的基环内向树。
现在我们只有\(i\rightarrow a_i\)建出来的图,所以我们倒过来考虑。
如果这不是由若干个环和基环内向树构成的森林,那么答案为\(0\)。
求出对于任意一个\(l\),有多少个环长度为\(l\),然后利用do求出对于一个环长而言的方案数,最后再根据乘法原理乘起来就好了。
假如现在长度为\(l\),设\(f_i\)表示有\(i\)个长度为\(l\)的环的方案数,那么有:
然后就只需要考虑基环内向树的方案数了。
我们把每条链(包括环上的点)单独拿出来,那么这个环会被剖成很多份。
假设一条链长为\(l\),从这条链顺时针的一段环的长度为\(d\),那么:
若\(d\le l\),则答案为\(0\);若\(d=l\),则方案数为\(1\);若\(d>l\),则方案数为\(2\)。
然后根据乘法原理把基环内向树的答案和环的答案乘在一起就好了。
#include<cstdio>
#include<cctype>
const int N=100007,P=1000000007;
int a[N],deg[N],vis[N],pre[N],cnt[N],f[N];
int read(){int x=0,c=getchar();while(isspace(c))c=getchar();while(isdigit(c))(x*=10)+=c&15,c=getchar();return x;}
int add(int a,int b){return a+=b-P,a+(a>>31&P);}
int mul(int a,int b){return 1ll*a*b%P;}
int main()
{
int n=read(),ans=1;
for(int i=1;i<=n;++i) ++deg[a[i]=read()];
for(int i=1,p;(p=i)<=n;++i)
{
if(deg[i]>2) return puts("0"),0;
if(deg[i]<2||vis[i]) continue;
do
{
if(vis[p]) return puts("0"),0;
vis[p]=1,pre[a[p]]=p,p=a[p];
}while(p^i);
}
for(int i=1,p,l1,l2;i<=n;++i)
if(!deg[i])
{
for(p=i,l1=0,l2=0;!vis[p];p=a[p]) vis[p]=1,++l1;
do ++l2,p=pre[p]; while(deg[p]^2);
if(l1<l2) ans=add(ans,ans); else if(l1>l2) return puts("0"),0;
}
for(int i=1;i<=n;++i)
if(!vis[i])
{
int p=i,l=0;
do ++l,p=a[p],vis[p]=1; while(p^i);
++cnt[l];
}
for(int i=1;i<=n;ans=mul(ans,f[cnt[i++]]))
{
f[0]=1,f[1]=1+(i^1&&i&1);
for(int j=2;j<=cnt[i];++j) f[j]=add(mul(i,mul(j-1,f[j-2])),mul(f[1],f[j-1]));
}
printf("%d",ans);
}
标签:operator link inline http 乘法 利用 include read next
原文地址:https://www.cnblogs.com/cjoierShiina-Mashiro/p/12639824.html