神仙题。

排列计数,一种常见的做法是 \(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. MQ的幂等性和解决方案

    1.幂等性 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同.通俗的讲就一个数据,或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,不能出错:类似于数据库中的乐 ...

  2. Unity Settings Deamon crash in 16.04 every time after boot

    安装ubuntu 16.04的时候,出现这样一个错误: unity-settings-deamon crashed with SIGSEGV in up_exported_dae (can't rea ...

  3. 世界GDP数据可视化

    各国GDP数据可视化 数据来自世界银行 导入资源包,如下: Pandas, numpy, seaborn 和 matplotlib import pandas as pd import numpy a ...

  4. JDBC释放数据库连接

    try(){}写法会自动关闭连接 String sql = "select password from user where name = ?"; try(Connection c ...

  5. Linux帮助——重要文件

    Linux帮助——重要文件 摘要:本文主要介绍了Linux系统中常用的文件. 查看系统安装版本 文件 /etc/redhat-release 内容 [root@localhost ~]# cat /e ...

  6. java核心技术第五篇之事务和MVC模式

    第一部分:事务1.事务的简介: 1.1 在一组操作中(比如增加操作,修改操作),只有增加和修改操作都成功之后,这两个操作才能真正的成功. ,如果这两个操作中,有一个失败了,这两个操作都失败了. 1.2 ...

  7. django5-书籍与出版社关联外键

    1.外键相关 一对多的概念 ,这里是一个出版社对应本书籍 ! 设计表使用model models.ForeignKey('关联一', on_delete=models.CASCADE) #给多设置外键 ...

  8. Discuz 数据库各表的作用

    pre_common_addon 插件扩展中心服务商表 pre_common_admincp_cmenu 后台管理面板,自定义常用菜单表 pre_common_admincp_group 后台团队职务 ...

  9. Java实现单链表反转操作

    单链表是一种常见的数据结构,由一个个节点通过指针方式连接而成,每个节点由两部分组成:一是数据域,用于存储节点数据.二是指针域,用于存储下一个节点的地址.在Java中定义如下: public class ...

  10. Angular 学习笔记(三)

    调试时抓取作用域: 1.右键选取审查元素,调出 debugger(或按 F12) 2.调试器允许用变量 $0 来获取当前选取的元素 3.在 console 中执行 angular.element($0 ...