AGC008E Next or Nextnext
Posted cjoiershiina-mashiro
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AGC008E Next or Nextnext相关的知识,希望对你有一定的参考价值。
Link
如果我们(i
ightarrow p_i)建边,那么最后会得到一些有向环。
对于上述的每一个环,如果我们(i
ightarrow a_i)建边,那么此时(a_i)为(i)在顺时针方向下的第(1operatorname{or}2)个点,也就是(a_i=p_ivee a_i=p_{p_i})。
那么此时有四种情况:
若(a_i=p_i),那么这个环不变。
若(a_i=p_{p_i}),那我们对环长的奇偶性分开讨论。
若环长为奇数,那么这个环变成元素相同的另一个环。
若环长为偶数,那么这个环会被拆成两个环。
否则部分(a_i=p_i),部分(a_i=p_{p_i}),那么这个环会被拆成一个环和若干个指向环的链构成的基环内向树。
现在我们只有(i
ightarrow a_i)建出来的图,所以我们倒过来考虑。
如果这不是由若干个环和基环内向树构成的森林,那么答案为(0)。
求出对于任意一个(l),有多少个环长度为(l),然后利用do求出对于一个环长而言的方案数,最后再根据乘法原理乘起来就好了。
假如现在长度为(l),设(f_i)表示有(i)个长度为(l)的环的方案数,那么有:
然后就只需要考虑基环内向树的方案数了。
我们把每条链(包括环上的点)单独拿出来,那么这个环会被剖成很多份。
假设一条链长为(l),从这条链顺时针的一段环的长度为(d),那么:
若(dle 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);
}
以上是关于AGC008E Next or Nextnext的主要内容,如果未能解决你的问题,请参考以下文章
[agc008E]Next or Nextnext-[dp+思考题]