題目如有雷同,純屬巧合......
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):
數學歸納法: