@description@

给定一个长度为 N 的序列 a,问有多少排列 p,满足对于每一个 i,都有 \(a_i = p_i\) 或 \(a_i = p_{p_i}\) 成立。

原题传送门。

@solution@

为了更直观地理解问题,不妨建个图:连有向边 \(i -> p_i\)。

对于任意序列 a,这样建出的图是一个基环树森林;对于排列 p,这样建出的图是若干个环。

当 \(a_i = p_i\) 或 \(a_i = p_{p_i}\) 成立时,相当于我们把排列 p 对应的图的某个边 \(i -> p_i\) 替换成 \(i -> p_{p_i}\),最终得到序列 a 对应的图。

如果将某一个奇环的边全部替换,将得到另一个大小相同的奇环。

如果将某一个偶环的边全部替换,将得到两个大小为原先一半的环。

如果将某一个环的边不完全替换(不包括不替换的情况),一定得到一个基环树。

那么我们可以将 a 序列中相同大小的环放在一起计数,把 a 序列中每一个基环树进行计数。

环的情况一定是全部替换/全部不替换,上面的讨论已经包含了所有情况。

考虑基环树。首先你自己手玩一下,发现基环树如果要合法,一定是一个环 + 某些环上的点延伸出去恰好一条链。

其实比较容易理解。替换后每个点的入度最多为 2,且入度为 2 的一定是环上的点。

理论上延伸出去一条链的环上的点会贡献 2 倍方案数:可能延伸出去的链是被替换的;可能环上的入边是被替换的。但是有不合法的情况。

不妨记延伸出去链长为 x,顺着入边走找到的最近的有链延伸出去的点距离为 y。

当 x < y 时,延伸出去的链才可能是被替换的;当 x <= y 时,环上的入边才可能是被替换的。

这个自己手玩一下就能发现了。

@accepted code@

#include <cstdio>
#include <algorithm>
using namespace std; #define ILLEGAL puts("0"), exit(0) const int MAXN = 100000;
const int MOD = int(1E9) + 7;
const int INV2 = (MOD + 1) / 2; int pow_mod(int b, int p) {
int ret = 1;
for(int i=p;i;i>>=1,b=1LL*b*b%MOD)
if( i & 1 ) ret = 1LL*ret*b%MOD;
return ret;
} int fct[MAXN + 5], ifct[MAXN + 5], f[MAXN + 5];
int comb(int n, int m) {
return 1LL*fct[n]*ifct[m]%MOD*ifct[n-m]%MOD;
}
void init() {
fct[0] = 1;
for(int i=1;i<=MAXN;i++)
fct[i] = 1LL*fct[i-1]*i%MOD;
ifct[MAXN] = pow_mod(fct[MAXN], MOD - 2);
for(int i=MAXN-1;i>=0;i--)
ifct[i] = 1LL*ifct[i+1]*(i+1)%MOD;
f[0] = 1;
for(int i=2;i<=MAXN;i+=2)
f[i] = 1LL*comb(i, 2)*f[i-2]%MOD*pow_mod(i/2, MOD-2)%MOD;
} int a[MAXN + 5], N;
bool tag[MAXN + 5], vis[MAXN + 5];
int cnt[MAXN + 5], b[MAXN + 5], ind[MAXN + 5]; int main() {
init(), scanf("%d", &N);
for(int i=1;i<=N;i++)
scanf("%d", &a[i]), ind[a[i]]++;
for(int i=1;i<=N;i++)
if( ind[i] >= 3 ) ILLEGAL;
for(int i=1;i<=N;i++) {
if( ind[i] == 2 ) {
if( tag[i] ) continue;
int p = i;
while( !vis[p] )
vis[p] = true, p = a[p];
if( p != i ) ILLEGAL;
p = i;
do {
tag[p] = true, p = a[p];
}while( p != i );
}
}
for(int i=1;i<=N;i++) {
if( ind[i] == 0 ) {
int t = 0, p = i;
while( !tag[p] )
vis[p] = true, t++, p = a[p];
b[p] = t;
}
}
int ans = 1;
for(int i=1;i<=N;i++) {
if( b[i] ) {
int t = 0, p = i;
do {
t++, p = a[p];
}while( !b[p] );
if( t < b[p] ) ILLEGAL;
else if( t > b[p] ) ans = 2LL*ans%MOD;
}
}
for(int i=1;i<=N;i++) {
if( !vis[i] ) {
int t = 0, p = i;
do {
vis[p] = true, t++, p = a[p];
}while( !vis[p] );
cnt[t]++;
}
}
for(int i=1;i<=N;i++) {
int del = 0;
for(int j=0;j<=cnt[i];j+=2) {
int tmp = 1;
tmp = 1LL*tmp*comb(cnt[i], j)%MOD*f[j]%MOD*pow_mod(i, j/2)%MOD;
if( (i & 1) && i != 1 ) {
tmp = 1LL*tmp*pow_mod(2, cnt[i] - j)%MOD;
}
del = (del + tmp) % MOD;
}
ans = 1LL*ans*del%MOD;
}
printf("%d\n", ans);
}

@details@

其实细节比较多。不过这道题最奇妙的是数形结合利用建图简化思维。

@atcoder - AGC008E@ Next or Nextnext的更多相关文章

  1. [AGC008E] Next or Nextnext [环套树森林+结论讨论]

    题面 传送门 思路 p到a 首先,本题中如果对于所有的$i$,连边$<i,p_i>$,那么可以得到一批环 那么这个题另外一点就是,可以变成连边$<i,p_{p_i}>$ 我们分 ...

  2. AGC008E Next or Nextnext(组合计数,神奇思路)

    神仙题. 排列计数,一种常见的做法是 \(i\) 向 \(p_i\) 连边. 然而这里这个就逼迫我们只能从 \(i\) 向 \(a_i\) 连边. 不过没关系,考虑从 \(i\) 向 \(p_i\) ...

  3. AtCoder刷题记录

    构造题都是神仙题 /kk ARC066C Addition and Subtraction Hard 首先要发现两个性质: 加号右边不会有括号:显然,有括号也可以被删去,答案不变. \(op_i\)和 ...

  4. NOIp2018模拟赛四十

    今天太晚了...题解到时候补吧(flag立好) 成绩:100+0+0=100 感觉A题本质暴力贪心?C题一道水题我居然没做...亏爆 A:[agc011e]increasing numbers B:[ ...

  5. Atcoder Grand Contest 008 E - Next or Nextnext(乱搞+找性质)

    Atcoder 题面传送门 & 洛谷题面传送门 震惊,我竟然能独立切掉 AGC E 难度的思维题! hb:nb tea 一道 感觉此题就是找性质,找性质,再找性质( 首先看到排列有关的问题,我 ...

  6. AGC008E:Next or Nextnext

    传送门 考虑转化成图论问题,\(i\) 向 \(p_i\) 连边,那么合法方案一定是形成了若干个简单环或自环 考虑一个环内的情况: 如果 \(a_i=p_i\),那么 \(i\) 向 \(a_i\) ...

  7. AtCoder Grand Contest 008

    AtCoder Grand Contest 008 A - Simple Calculator 翻译 有一个计算器,上面有一个显示按钮和两个其他的按钮.初始时,计算器上显示的数字是\(x\),现在想把 ...

  8. AtCoder Regular Contest 061

    AtCoder Regular Contest 061 C.Many Formulas 题意 给长度不超过\(10\)且由\(0\)到\(9\)数字组成的串S. 可以在两数字间放\(+\)号. 求所有 ...

  9. AtCoder Grand Contest 001 C Shorten Diameter 树的直径知识

    链接:http://agc001.contest.atcoder.jp/tasks/agc001_c 题解(官方): We use the following well-known fact abou ...

随机推荐

  1. ios]企业开发者账号申请

    1. 先打电话到“华夏邓白氏公司”(上海:400-820-3536 北京:400-810-3531 广州:800-830-9032),我打的是北京分部的电话,就说自己因为申请apple开发者账号,需要 ...

  2. 用了这么多年的 Java 泛型,你对它到底有多了解?

    作为一个 Java 程序员,日常编程早就离不开泛型.泛型自从 JDK1.5 引进之后,真的非常提高生产力.一个简单的泛型 T,寥寥几行代码, 就可以让我们在使用过程中动态替换成任何想要的类型,再也不用 ...

  3. HDU2819

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2819 题目大意: 给出一个N*N的0/1矩阵,只能交换整行或者整列,问最少交换多少次可以变成一个主对角 ...

  4. vue-codemirror + Java Compiler实现Java Web IDE

    背景 最近同事告诉我一个很有趣的需求:让用户(应用场景中,一般为其他开发者)自己填入Java代码片段,代码片段的内容为已经规定好的模板类的继承类,实现模板类定义的方法.我们的项目要实现动态编译代码片段 ...

  5. HTML5移动端最新兼容问题解决方案

    1.安卓浏览器看背景图片,有些设备会模糊.用同等比例的图片在PC机上很清楚,但是手机上很模糊,原因是什么呢?经过研究,是devicePixelRatio作怪,因为手机分辨率太小,如果按照分辨率来显示网 ...

  6. Python_OpenCV学习记录01安装

    Python照样快! 众所周知,虽然Python语法简洁,编写高效,但相比C/C++运行慢很多.然 而Python还有个重要的特性:它是一门胶水语言!Python可以很容易地扩展 C/C++.Open ...

  7. java远程执行linux服务器上的shell脚本

    业务场景:需要从服务器A中新增的文件同步至本地服务器,服务器A中内存有限,需同步成功之后清除文件. Java调用远程shell脚本,需要和远程服务器建立ssh链接,再调用指定的shell脚本. 1.创 ...

  8. 实验三:Linux系统用户管理及VIM配置

    项目 内容 这个作业属于哪个课程 班级课程的主页链接 这个作业的要求在哪里 作业要求链接地址 学号-姓名 17043133-木腾飞 学习目标 1.学习Linux系统用户管理2.学习vim使用及配置 实 ...

  9. Azure Kubernetes 服务 (AKS)

    一.首先创建集群 1,注意:一定要选择Kubernets Service(红框处),上面的那一堆虚拟机都没有用, 2,设置好相关属性,集群大小可后面更改节点数,但是节点的大小不可更改 二.登陆集群 在 ...

  10. 3.key的操作

    我们之前使用Redis简单存储了三个参数: 在语句set name jack中,其中name就是一个key.我们Java中的变量名是有一定规则的,比如组成内容可以是“数字”,“字母”以及“下划线”. ...