神仙题。

排列计数,一种常见的做法是 \(i\) 向 \(p_i\) 连边。

然而这里这个就逼迫我们只能从 \(i\) 向 \(a_i\) 连边。

不过没关系,考虑从 \(i\) 向 \(p_i\) 连边的图(为方便叫 \(G_1\))和从 \(i\) 向 \(a_i\) 连边的图(为方便叫 \(G_2\))的区别。

首先 \(G_1\) 中每个点入度和出度都是 \(1\),所以是一堆环构成的。

考虑一个环:(下面建议画图,懒的建议看 litble 学姐的博客,自己不敢直接把图拿过来)

  • 如果上面所有点都满足 \(p_i=a_i\),那么这个环在 \(G_2\) 中也出现了,长得一样。
  • 如果上面所有点都满足 \(p_{p_i}=a_i\),且这个环长度为奇数,那么有一个相同长度的环在 \(G_2\) 中出现了,但是长得不一样。
  • 如果上面所有点都满足 \(p_{p_i}=a_i\),且这个环长度为偶数,那么有两个长度都为这个环长度的一半的环在 \(G_2\) 中出现了。
  • 如果上面的点又有满足 \(p_i=a_i\) 的,又有满足 \(p_{p_i}=a_i\) 的,那么会有一个点数相同的基环内向树在 \(G_2\) 中出现。(这种情况比较复杂,等会再说)

现在知道了 \(G_2\) ,问 \(G_1\) 的方案数。

环和基环树独立。先看环。

令长度为 \(i\) 的环有 \(cnt_i\) 个。每个环要么是单独在 \(G_1\) 中出现,要么是与另一个环拼成一个大环在 \(G_1\) 中出现。

枚举与别的环一起拼的环的个数 \(2j\),那么把下面这些全步乘起来:

  • \(j\ne 0\) 时,先选出这些环,\(\binom{cnt_i}{2j}\);
  • \(j\ne 0\) 时,然后想象成是个二分图,枚举左边的环是哪些,再把右边的环分给左边的环。注意实际上没有顺序,所以要再除掉一堆 \(2\)。\(\frac{\binom{2j}{j}j!}{2^j}\);
  • \(j\ne 0\) 时,每对环有 \(i\) 种拼法,\(i^j\);
  • \(i\) 为奇数且 \(i\ne 1\) 时,没有与别的环拼起来的环可以以两种形态在 \(G_1\) 中出现,\(2^{cnt_i-2j}\)。

再看基环树。基环树之间也相互独立。

挂在基环树上的一堆链要压到环上。图的话,建议继续看学姐的博客。

抓比较重要的几点来说:1、每条链不能重叠,所以压链的话不能超过上一个有链的点。2、如果不考虑 1 中的限制,每条链有两种压法,上面的第一个点在 \(G_1\) 中直接连向 \(u\) 和两步连向 \(u\)。

所以说,每条链的压法只有 \(0,1,2\),且互相独立。直接乘起来。

一些无解情况也可以很简单判了:在环上的点入度 \(\le 2\),不在环上的点入度 \(\le 1\)。联想一下基环树的方案数算法应该很好理解。

时间复杂度 \(O(n\log n)\),如果有闲心也可以做到 \(O(n)\)。

#include<bits/stdc++.h>
using namespace std;
const int maxn=100010,mod=1000000007,inv2=500000004;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
int x=0,f=0;char ch=getchar();
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,a[maxn],deg[maxn],ans=1,stk[maxn],tp,q[maxn],h,r,len[maxn],seq[maxn],tot,cnt[maxn];
int fac[maxn],inv[maxn],invfac[maxn];
bool vis[maxn],ins[maxn],cyc[maxn];
void dfs(int u){
if(vis[u]){
if(ins[u]){
ROF(i,tp,1){
cyc[stk[i]]=true;
if(stk[i]==u) break;
}
}
return;
}
vis[u]=ins[u]=true;
stk[++tp]=u;
dfs(a[u]);
ins[u]=false;
}
bool dfs2(int u){
if(vis[u]) return true;
vis[u]=true;
seq[++tot]=len[u];
return dfs2(a[u]) && !len[u];
}
inline int C(int n,int m){
return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}
inline int qpow(int a,int b){
int ans=1;
for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ans=1ll*ans*a%mod;
return ans;
}
int main(){
n=read();
FOR(i,1,n) a[i]=read(),deg[a[i]]++;
FOR(i,1,n) dfs(i);
FOR(i,1,n) if(cyc[i] && deg[i]>=3|| !cyc[i] && deg[i]>=2) return puts("0"),0;
h=1;r=0;
FOR(i,1,n) if(!deg[i]) q[++r]=i;
while(h<=r){
int u=q[h++];
len[a[u]]=len[u]+1;
if(!cyc[a[u]]) q[++r]=a[u];
}
MEM(vis,0);
FOR(i,1,n) if(cyc[i] && !vis[i]){
tot=0;
if(dfs2(i)) cnt[tot]++;
else{
int pre=0;
FOR(j,1,tot) if(seq[j]){
if(pre){
int at=j-seq[j];
if(at<pre) return puts("0"),0;
if(at>pre && tot>=2) ans=2*ans%mod;
}
pre=j;
}
FOR(j,1,tot) if(seq[j]){
int at=j-seq[j]+tot;
if(at<pre) return puts("0"),0;
if(at>pre && tot>=2) ans=2*ans%mod;
break;
}
}
}
fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
FOR(i,2,n){
fac[i]=1ll*fac[i-1]*i%mod;
inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
invfac[i]=1ll*invfac[i-1]*inv[i]%mod;
}
FOR(i,1,n) if(cnt[i]){
int s=0;
FOR(j,0,cnt[i]/2){
int x=1ll*C(cnt[i],2*j)*C(2*j,j)%mod*fac[j]%mod;
if(j) x=1ll*x*qpow(inv2,j)%mod*qpow(i,j)%mod;
if(i%2==1 && i!=1) x=1ll*x*qpow(2,cnt[i]-2*j)%mod;
s=(s+x)%mod;
}
ans=1ll*ans*s%mod;
}
printf("%d\n",ans);
}

AGC008E Next or Nextnext(组合计数,神奇思路)的更多相关文章

  1. [总结]数论和组合计数类数学相关(定理&证明&板子)

    0 写在前面 0.0 前言 由于我太菜了,导致一些东西一学就忘,特开此文来记录下最让我头痛的数学相关问题. 一些引用的文字都注释了原文链接,若侵犯了您的权益,敬请告知:若文章中出现错误,也烦请告知. ...

  2. bzoj 2281 [Sdoi2011]黑白棋(博弈+组合计数)

    黑白棋(game) [问题描述] 小A和小B又想到了一个新的游戏. 这个游戏是在一个1*n的棋盘上进行的,棋盘上有k个棋子,一半是黑色,一半是白色. 最左边是白色棋子,最右边是黑色棋子,相邻的棋子颜色 ...

  3. WC集训DAY2笔记 组合计数 part.1

    目录 WC集训DAY2笔记 组合计数 part.1 基础知识 组合恒等式 错排数 卡特兰数 斯特林数 伯努利数 贝尔数 调和级数 后记 补完了几天前写的东西 WC集训DAY2笔记 组合计数 part. ...

  4. BZOJ1079 [SCOI2008]着色方案[组合计数DP]

    $有a_{1}个1,a_{2}个2,...,a_{n}个n(n<=15,a_{n}<=5),求排成一列相邻位不相同的方案数.$ 关于这题的教训记录: 学会对于复杂的影响分开计,善于发现整体 ...

  5. BZOJ 4555: [Tjoi2016&Heoi2016]求和 [分治FFT 组合计数 | 多项式求逆]

    4555: [Tjoi2016&Heoi2016]求和 题意:求\[ \sum_{i=0}^n \sum_{j=0}^i S(i,j)\cdot 2^j\cdot j! \\ S是第二类斯特林 ...

  6. BZOJ 4555: [Tjoi2016&Heoi2016]求和 [FFT 组合计数 容斥原理]

    4555: [Tjoi2016&Heoi2016]求和 题意:求\[ \sum_{i=0}^n \sum_{j=0}^i S(i,j)\cdot 2^j\cdot j! \\ S是第二类斯特林 ...

  7. 【BZOJ5491】[HNOI2019]多边形(模拟,组合计数)

    [HNOI2019]多边形(模拟,组合计数) 题面 洛谷 题解 突然特别想骂人,本来我考场现切了的,结果WA了几个点,刚刚拿代码一看有个地方忘记取模了. 首先发现终止态一定是所有点都向\(n\)连边( ...

  8. 【BZOJ5323】[JXOI2018]游戏(组合计数,线性筛)

    [BZOJ5323][JXOI2018]游戏(组合计数,线性筛) 题面 BZOJ 洛谷 题解 显然要考虑的位置只有那些在\([l,r]\)中不存在任意一个约数的数. 假设这样的数有\(x\)个,那么剩 ...

  9. 【BZOJ5305】[HAOI2018]苹果树(组合计数)

    [BZOJ5305][HAOI2018]苹果树(组合计数) 题面 BZOJ 洛谷 题解 考虑对于每条边计算贡献.每条边的贡献是\(size*(n-size)\). 对于某个点\(u\),如果它有一棵大 ...

  10. 【BZOJ3142】[HNOI2013]数列(组合计数)

    [BZOJ3142][HNOI2013]数列(组合计数) 题面 BZOJ 洛谷 题解 唯一考虑的就是把一段值给分配给\(k-1\)天,假设这\(k-1\)天分配好了,第\(i\)天是\(a_i\),假 ...

随机推荐

  1. DirectShow 应用开发过程

    本文准备总结一些 Direct Show 常用的API接口函数,方便以后查询回忆.如果这里没有你想了解的函数,你可以自行搜索MSDN + 函数名去 MSDN 查找你想要了解的函数,也可以查看百度百科相 ...

  2. C#读取匿名对象的属性值的方法总结

    目录 1.通过反射的方式获取属性值 2.新建个扩展方法,将object转成对应的匿名对象 通过new出匿名对象,可以直接调用该匿名对象的属性名,获取属性值. var objUser = new {Na ...

  3. 【JS】JS校验密码复杂度(必须包含字母、数字、特殊符号)

    #场景一:密码中必须包含大小写 字母.数字.特称字符,至少8个字符,最多30个字符: var pwdRegex = new RegExp('(?=.*[0-9])(?=.*[A-Z])(?=.*[a- ...

  4. php精确计算

    php BC高精确度函数库 结果: php一般的取余 只是除以整数 bc精度取余 精确到了小数

  5. Fuzzy finder(fzf+vim) 使用入门指南

    今天无意中尝试了fzf,才发现这个工具的威力无穷,毕竟是非常好的工具,第一次都把它的优点都释放出来也不现实,先熟悉一下吧,后面在实战中再不断地学习总结. 它是什么: Fuzzy finder 是一款使 ...

  6. 一份完整的PyCharm图解教程

    PyCharm 是一种 Python IDE,可以帮助程序员节约时间,提高生产效率.那么具体如何使用呢?本文从 PyCharm 安装到插件.外部工具.专业版功能等进行了一一介绍,希望能够帮助到大家. ...

  7. 栈与后缀表达式C实现

    #include<stdio.h> #include<stdlib.h> typedef char datatype; typedef struct stack { int t ...

  8. sql server相邻表记录交换(单双两两交换)

    在博客园的博问中看到了一个这样的提问:一个表中有id和name两个字段,id是连续非空不重复的,想要交换相邻表记录的name值(单双两两交换). 另外,如果最后的单独行没有对应的下一行匹配记录的话,就 ...

  9. gitlab与jenkins结合构建持续集成

    Jenkins是java编写,需要安装JDK,这里采用 yum 安装,对版本有需求的,可以到 oracle 官网下载 JDK. yum install -y java-1.8.0-openjdk 一. ...

  10. SPA项目搭建及嵌套路由

    Vue-cli: 什么是vue-cli? vue-cli是vue.js的脚手架,用于自动生成vue.js+webpack的项目模板,创建命令如下: vue init webpack xxx 注1:xx ...