题目如有雷同,纯属巧合......
MT与OI
【问题描述】
在MT刚接触到了冒泡排序的时候,觉得这个东西太慢了,但是加上break的效果怎么样呢?于是他开始考虑这样一个问题:任意一个N的排列中,有多少种需要恰好扫K次才能使得数列从小到大排列。所谓扫就是第一重循环,当数列有序后程序会自动退出循环。
冒泡排序代码:
Var
a:array[0..n] of longint;
For i:=1 to n-1 do
For j:=n downto i+1 do
If a[j]<a[j-1] then
Swap(a[j],a[j-1]);
【输入格式】
第一行一个数T,表示数据组数
接下来T行,每行两个整数N,K
【输出格式】
T个数,表示答案mod 1000000007
【样例输入】
3
3 0
3 1
3 2
【样例输出】
1
3
2
【数据说明】
30%:T<=10,N<=7
100%:T ≤ 100,000
1 ≤ N ≤ 1,000,000, 0 ≤ K ≤ N – 1
我一开始以为是动规,结果花了20分钟打表,30分钟找规律,然后打了个40分的动规(当时居然不准重测,说了空间是512M结果只允许128M,我就全超了,该空间可以对40分......),后来有位神牛打了一份解题报告(没看懂,复制了下来):
公式:
K!((K + 1) ^ (N - K) - K ^ (N - K))
好吧,现在让我们来证明一下。
首先,冒泡的两种写法对答案是没有影响的。
一:
Var
a:array[0..n] of longint;
For i:=1 to n-1 do
For j:=n downto i+1 do
If a[j]<a[j-1] then
Swap(a[j],a[j-1]);
二:
Var
a:array[0..n] of longint;
For i:=n-1 downto 1 do
For j:=1 to i do
If a[j]>a[j+1] then
Swap(a[j],a[j+1]);
为方便叙述,这里根据第二种写法讨论,
首先定义函数d(x),对于1~N的一个排列,d(x)表示第x个数(位置)前面有多少个数字大于该数。(对应到第一种写法就是第x个数后面有多少个数小于该数,因此两问题是一样的)
比如说对于3 2 4 1 5,有d(1) = 0,d(2) = 1,d(3) = 0,d(4) = 3,d(5) = 0。
现在我们来证明d(x)函数的两条性质:
(一)对于一个排列,对于所有x <= N,有d(x) = 0是这个排列是有序的充要条件。
(二)冒泡排序的每次扫描的结果是,对于非零的d(x)值,这个位置的d(x)会且只会减少1。
我们得出对于1~n的一个排列,它所需要的冒泡排序的扫描次数为
K = max (d(i), 1 <= i <= N)
而这个结论很显然,因为只有经过K次扫描,所有位置的d值才能都变为0。
到此,我们成功地将冒泡排序的次数问题转化为d(x)值满足条件的数列的问题。原问题也就转化成了有多少个排列使得其中最大的d(x)值恰好为K。然而这也是复杂的,所以说我们不妨先解决有多少个排列使得其中最大的d(x)值不大于K。
首先可以确定N >= K + 1,否则不可能出现某个位置前面有K个数大于它。
然后决定原数列中1的位置。显而易见,如果最小数的位置为x,则其d(x) = x - 1。而d(x) <= K,故x <= K + 1,也就是说1有K + 1种放置方法;而放置2的时候,我们完全可以考虑一个新的排列2~N,这时2有K + 1种放置方法,然后再把1插到位置1~K + 1,而不影响其它数的d值。所以说,前N - K个数的放置方法的种类有
(K + 1) ^ (N - K)
之后只需要考虑N - K + 1 ~ N的排列即可。然而,由于整个数列只有K个数字,不可能出现某个d值大于K + 1。所以说排列方法有K!种。故,所有位置d值不大于K的排列的方案数有
K!((K + 1) ^ (N - K))
但是这是不大于K的排列数量,恰好为K的有怎么办呢?很简单,只需要减去不大于K - 1的排列数量便可。所以最后的答案为
K!((K + 1) ^ (N - K)) - (K - 1)!(K ^ (N - K + 1))
化简之后我们就得到
K!((K + 1) ^ (N - K) - K ^ (N - K))
这就是原来的式子,它的正确性就证明完毕。
以上为引用 ,然后快速幂就做出来了......
动规如下(N*K):
数学归纳法: