问题描述

给定你一个 \(1 \sim n\) 的排列 \(\{p_i\}\),可进行若干次操作,每次选择两个整数 \(x,y\),交换 \(p_x,p_y\)。

请你告诉穰子,用最少的操作次数将给定排列变成单调上升的序列 \(1,2,\ldots,n\),有多少种方式呢?请输出方式数对 \(10^9+9\) 取模的结果。

输入格式

第一行一个整数 \(T\) 代表数据组数。

每一组测试数据,第一行是一个整数 \(n\) 代表排列中的元素个数,第二行 \(n\) 个整数,是这个排列。

输入数据中测试数据间也许存在空行,请务必注意。

输出格式

\(T\) 行,一行一个整数,代表这组测试数据的答案。

样例一

input

1
3
2 3 1

output

3

explanation

至少需要两步,有三种操作方式。

  • 先换 \(2,3\),再换 \(3,1\)。
  • 先换 \(2,1\),再换 \(3,2\)。
  • 先换 \(3,1\),再换 \(2,1\)。

数据范围与约定

对于 \(50\%\) 的数据,\(1 \leq n \leq 10\)。

对于 \(100\%\) 的数据,\(1 \leq n \leq 100000\),\(T=100\)。

时间限制: \(1\mathrm{s}\)

内存限制: \(256\mathrm{MB}\)

来源

ipsc2016c


题解

对于每个点 \(i\),将他和他的身上的权值 \(p_i\) 代表的那个点连边。最后构成了许多许多的长度 \(\in [1,n]\) 的环。

把长度为 \(n\) 的环变成 \(n\) 个长度为 \(1\) 的自环,最少需要 \(n-1\) 次操作。这可以用数学归纳法证明。

记 \(F_n\) 为把长度为 \(n\) 的环变成 \(n\) 个长度为 \(1\) 的自环,在步数最小的前提下的操作方式数。

要想把长度为 \(n\) 的环变成 \(n\) 个长度为 \(1\) 的自环,首先肯定要将它划分成长度为 \(x,y\) 且 \(x+y=n\) 的两个环。

那么,有多少种划分方式呢?我们记把长度为 \(n\) 的环划分成长度为 \(x,y\) 且 \(x+y=n\) 的两个环的方式数为 \(T(x,y)\)。 我们考虑这样一个图:

这个是交换了 \(1,5\)。你也可以交换 \(2,5\)。

我们发现,对于每个点,总有两个合适的点来和这个点交换,以达到划分的目的。因此,\(T(x,y)\) 为 \(2n/2=n\)。

但是,我们发现,当 \(n\) 为偶数且 \(x=y\) 时,这两个点会重合, \(T(x,y)\) 变成 \(n/2\)。

所以,

\[T(x,y)=\begin{cases}
n/2, & n \equiv 0 \pmod 2\ \mathrm{and}\ x=y\\
n, & \mathrm{others}
\end{cases}
\]

我们再考虑一下操作次序的问题。显然地,两个环之间互不影响。可以把一个环看成一类,用可重集排列数计算 \(x-1\) 步和 \(y-1\) 步之间怎么“交错”。“交错”的方法数依据公式,也就是 \((n-2)!/((x-1)!(y-1)!)\)。

因此,根据可重集排列数、加法原理、乘法原理,我们得到

\[F_n=\sum_{x+y=n} T(x,y)F_xF_y\dfrac{(n-2)!}{(x-1)!(y-1)!}
\]

用 dfs 找出所有的环,它们的长度为 \(l_1,l_2,\ldots,l_k\),则再考虑考虑“交错”的方法数,我们得到答案

\[\prod_{i=1}^{k}F_{l_i} \times \dfrac{(n-k)!}{\prod_{i=1}^k(l_i-1)!}
\]

阶乘肯定与模数 \(10^9+9\) 互素,因此用费马小定理求逆元即可。

这样的时间复杂度是 \(\mathrm{O}(n^2)\)。

俗话说打表是第一生产力。我们在研究 \(F_i\) 的规律时,意外地发现 \(F_i=i^{i-2}\)。(我也不会证明……如果您会证明,请联系我)。这样,时间复杂度变为 \(\mathrm{O}(n \log n)\)。

当然,如果你信不过这个规律,你也可以打一个 \(F_i\) 的表放到代码里头……这样的时间复杂度是 \(\mathrm{O}(\mathrm{It\ would\ be\ accepted})\)……

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
int n, uu, hea[100005], cnt, bel[100005], din, hav[100005], ans, T;
int jie[100005];
struct Edge{
int too, nxt;
}edge[200005];
const int mod=1e9+9;
void add_edge(int fro, int too){
edge[++cnt].nxt = hea[fro];
edge[cnt].too = too;
hea[fro] = cnt;
}
void dfs(int x, int c){
bel[x] = c;
for(int i=hea[x]; i; i=edge[i].nxt){
int t=edge[i].too;
if(!bel[t])
dfs(t, c);
}
}
int ksm(int a, int b){
if(!a) return 1;
if(b<0) return 1;
int re=1;
while(b){
if(b&1) re = ((ll)re * a) % mod;
a = ((ll)a * a) % mod;
b >>= 1;
}
return re;
}
int main(){
cin>>T;
jie[0] = 1;
for(int i=1; i<=100000; i++)
jie[i] = ((ll)jie[i-1] * i) % mod;
while(T--){
memset(hav, 0, sizeof(hav));
memset(bel, 0, sizeof(bel));
memset(hea, 0, sizeof(hea));
cnt = din = 0;
scanf("%d", &n);
for(int i=1; i<=n; i++){
scanf("%d", &uu);
add_edge(i, uu);
add_edge(uu, i);
}
for(int i=1; i<=n; i++)
if(!bel[i])
dfs(i, ++din);
for(int i=1; i<=n; i++)
hav[bel[i]]++;
ans = 1;
for(int i=1; i<=din; i++)
ans = ((ll)ans * ksm(hav[i], hav[i]-2)) % mod;
ans = ((ll)ans * jie[n-din]) % mod;
for(int i=1; i<=din; i++)
ans = ((ll)ans * ksm(jie[hav[i]-1], mod-2)) % mod;
cout<<ans<<endl;
}
return 0;
}

lfyzoj104 Counting Swaps的更多相关文章

  1. CH3602 Counting Swaps

    题意 3602 Counting Swaps 0x30「数学知识」例题 背景 https://ipsc.ksp.sk/2016/real/problems/c.html Just like yeste ...

  2. Counting swaps

    Counting swaps 给你一个1-n的排列,问用最少的交换次数使之变为递增排列的方案数\(mod\ 10^9+7\),1 ≤ n ≤ 10^5. 解 显然最少的交换次数不定,还得需要找到最小交 ...

  3. 洛谷P4778 Counting swaps 数论

    正解:数论 解题报告: 传送门! 首先考虑最终的状态是固定的,所以可以知道初始状态的每个数要去哪个地方,就可以考虑给每个数$a$连一条边,指向一个数$b$,表示$a$最后要移至$b$所在的位置 显然每 ...

  4. luogu P4778 Counting swaps

    计数套路题?但是我连套路都不会,,, 拿到这道题我一脸蒙彼,,,感谢@poorpool 大佬的博客的指点 先将第\(i\)位上的数字\(p_i\)向\(i\)连无向边,然后构成了一个有若干环组成的无向 ...

  5. LFYZOJ 104 Counting Swaps

    题解 #include <iostream> #include <cstdio> #include <algorithm> #include <cmath&g ...

  6. luoguP4778 Counting swaps

    题目链接 题解 首先,对于每个\(i\)向\(a[i]\)连边. 这样会连出许多独立的环. 可以证明,交换操作不会跨越环. 每个环内的点到最终状态最少交换步数是 \(环的大小-1\) 那么设\(f[i ...

  7. P4778 Counting Swaps 题解

    第一道 A 掉的严格意义上的组合计数题,特来纪念一发. 第一次真正接触到这种类型的题,给人感觉好像思维得很发散才行-- 对于一个排列 \(p_1,p_2,\dots,p_n\),对于每个 \(i\) ...

  8. 0x36 组合计数

    组合计算的性质: C(n,m)= m! / (n!(m-n)!) C(n,m)=C(m-n,m); C(n,m)=C(n,m-1)+C(n-1,m-1); 二项式定理:(a+b)^n=sigema(k ...

  9. 萌新笔记——Cardinality Estimation算法学习(二)(Linear Counting算法、最大似然估计(MLE))

    在上篇,我了解了基数的基本概念,现在进入Linear Counting算法的学习. 理解颇浅,还请大神指点! http://blog.codinglabs.org/articles/algorithm ...

随机推荐

  1. c51中的bit,SBIT

    在51单片机的0x20~0x2f,是bdata区既可以字节寻址又可以位寻址.用法: 1 先用bdata存储类型关键字 定义变量,注意其值就是地址 .unsigned char bdata MYBITS ...

  2. 安卓新的联网方式 Volley的使用(一)加载图片与 json

    最近刚接触安卓, 以前搞wp ,一对比起来 ,安卓怎么这么麻烦.联网必须要重新开一个线程才可以.而且加载网络图片也很麻烦...花了很久一直卡在快速滑动加载网络图片的listview上面 ,一直很纠结痛 ...

  3. js中对象与函数的关系

    首先什么是对象?根据W3C上面的解释JS中所有事物都是对象,对象是拥有属性和方法的数据,由此可以看出基本值类型不是对象(number.string.Boolean.Undefined),剩下的引用类型 ...

  4. IDEA Maven无法添加依赖到项目中

    IDEA--------->File-------->Setting------------>Maven 勾上即可,OK啦! 完美解决了

  5. Java Lambda表达式 Stream

    Stream Stream不是集合元素,它不是数据结构并不保存数据,而是有关算法和计算的,更像是一个高级版本的Iterator,原始版本的Iterator,用户只能显式地一个一个遍历元素并对其进行操作 ...

  6. Jenkins环境搭建(6)-修改自动化测试报告的样式

    写在最前: 我遇到一个问题,就是导出数据时,接口返回的数据是乱码,乱码如下图所示.问了开发,说是byte数据.这种情况,将response Data数据写入到报告中的话,在jenkins上运行时,提示 ...

  7. 【R语言进行数据挖掘】决策树和随机森林

    1.使用包party建立决策树 这一节学习使用包party里面的函数ctree()为数据集iris建立一个决策树.属性Sepal.Length(萼片长度).Sepal.Width(萼片宽度).Peta ...

  8. IOS博客

    http://www.cnblogs.com/lovecode/articles/2249548.html从这个人这里了解了一些关于uiview和uilayer的区别 以及对于渲染和动画也有了一些了解 ...

  9. (十)maven之排除冲突jar包

    排除冲突jar包 jar包冲突 <dependencies> <dependency> <groupId>org.springframework</group ...

  10. SAP不同的产品是如何支持用户创建自定义字段的

    我们从SAP CRM,Cloud for Customer(简称C4C)和S/4HANA这三个产品分别来看看. SAP CRM 我们使用所谓的Application Enhancement Tool( ...