[2021-09-09 T2] 就差⼀点——冒泡排序和反序表之间不为人知的秘密
就差一點解題報告
- description
- solution
- code
description
題目描述
冒泡排序是?個簡單的排序算法,其時間復雜度為O(n2)O(n^2)O(n2)
有?個大小為nnn的排列p1,...,pnp_1,...,p_np1?,...,pn?,?明想對這個排列進?冒泡排序,于是寫了下?這份代碼:
for(int i=1;i<=k;i++)for(int j=1;j<n;j++)if(p[j]>p[j+1]) swap(p[j],p[j+1]);細?的選手不難發現小明手抖把第?行中的nnn打成了kkk,所以當kkk比較小時,這份代碼可能會出錯
?明發現當這份代碼出錯時,可能就差?點就能把這個排列排序,他定義?個排列就差?點能被排序,當且僅當這個排列存在?個大小為n?1n-1n?1的上升子序列
小明想知道,對于給定的n,kn,kn,k,有多少種不同的排列滿?對這個排列運?上述代碼后,這個排列就差?點能被排序
由于答案可能很?,?明只需要知道答案對質數modmodmod取模的結果
輸入格式
本題?個測試點含有多組測試數據,第?行?個整數TTT,表示數據組數
接下來TTT行,每行333個整數,n,k,modn,k,modn,k,mod,意義同題意
輸出格式
共TTT行,對于每組測試數據,輸出一行一個整數表示答案
樣例
5 5 1 998244353 5 2 998244353 5 3 998244353 5 4 998244353 5 5 998244353 74 114 120 120 120數據范圍
1≤n,k,T≤5000,108≤mod≤109+71\le n,k,T\le5000,10^8\le mod\le 10^9+71≤n,k,T≤5000,108≤mod≤109+7
solution
-
DDG有個神奇DPDPDP,正確倒是正確,只是這是怎么想到的呢?
dpi,j=∑k=1j?1dpi?1,k+j?dpi,jdp_{i,j}=\sum_{k=1}^{j-1}dp_{i-1,k}+j*dp_{i,j}dpi,j?=∑k=1j?1?dpi?1,k?+j?dpi,j? (前iii個數,在xxx前面比xxx大的數的個數最大值為jjj 的序列)
因為一次冒泡排序,相當于處理了 在iii前面比iii大的數個數最多 的iii
-
卷爺找了三個強大的性質
最重要的性質就是,對于值屬于[i,n][i,n][i,n]的所有下標,將這些下標抽出來,然后根據值離散化
如果離散化后的序列需要ttt次變成單調上升,那么回到原序列,也只需要ttt次,單看這些下標,就會發現是單調上升的
結合這兩種思路,就來到了小兒子的通俗易懂的解法——反序表
反序表對于一個排列的定義為si=∑j=1i?1[pj>pi]s_i=\sum_{j=1}^{i-1}[p_j>p_i]si?=∑j=1i?1?[pj?>pi?]
- 即反序表的第iii位上的數值表示:在原排列中,第iii位以前的比第iii位值大的個數
e.g. 原序列3 2 4 1 5,反序表為0 1 0 3 0
反序表具有很多非常好的性質
-
顯然對于iii,si<is_i<isi?<i(嚴格小于);換言之,對于iii,其sis_isi?的取值為[0,i)[0,i)[0,i)共iii個
-
反序表與排列是一一對應的,那么原題要求排列個數,就轉化成了求反序表的個數
-
冒泡一輪排序會將 最大的 還沒在應在位置的值 放置在 其應在位置,然后這區間中的每個數位置都會前移一位,其在反序表的變化則為下標,值均減一(如果已經是000就不減)
換言之,一次冒泡排序后,每個數至多只會減少一
e.g.
原序列3 2 5 1 4,反序表為0 1 0 3 1
一次冒泡排序后
原序列3 2 1 4 5,反序表為0 1 2 0 0
555由位置333變到555,反序表改變的區間為[3,5][3,5][3,5]
-
反序表中iii位置上的值如果為sis_isi?,意味著至少需要sis_isi?次冒泡排序才能將原序列iii有序
這里的有序定義為,其前面的數全小于ta,其后面的數全大于ta
了解完反序表的性質后,就可以解決這道題了
-
考慮冒泡排序后,最后的序列是一個長為nnn的上升子序列(不差一點)
這時的反序表全是000,0,0,0,...,0
一次排序,反序表只能減一或者不減,kkk次排序最多減少kkk
也就是說想要最后反序表為000,其初始值不能超過kkk
即:?isi≤min?(i?1,k)\forall_i\quad s_i\le \min(i-1,k)?i?si?≤min(i?1,k)
將這些值域限制乘起來就是不同的反序表個數,也就是不同的排列個數
即:∏i=1n(min?(i?1,k)+1)\prod_{i=1}^n\Big(\min(i-1,k)+1\Big)∏i=1n?(min(i?1,k)+1)
-
考慮冒泡排序后,最后的序列是一個長為n?1n-1n?1的上升子序列(只差一點)
-
這時的反序表形如0,0,...,1,1,...,1,0,0,...,0
e.g. 最后序列為4 1 2 3 5,反序表為0 1 1 1 0
最后為000說明初始反序表的值不超過kkk
最后為111說明初始反序表的值不超過k+1k+1k+1
注意:sis_isi?能取到k+1k+1k+1的iii是有限制的,僅為[k+2,n][k+2,n][k+2,n],共n?(k+2)+1=n?k?1n-(k+2)+1=n-k-1n?(k+2)+1=n?k?1個
(不要忘記si<is_i<isi?<i的約束)
考慮枚舉這段111的長度lenlenlen
這段長度的選擇方案相當于在總長為n?k?1n-k-1n?k?1中擺下lenlenlen的放置方案,顯然為n?k?1?len+1=n?k?lenn-k-1-len+1=n-k-lenn?k?1?len+1=n?k?len種
剩下的n?k?1?lenn-k-1-lenn?k?1?len個數反序表都不超過kkk,可選為[0,k][0,k][0,k]共k+1k+1k+1個
這些數生成的反序表組合為(k+1)n?k?1?len(k+1)^{n-k-1-len}(k+1)n?k?1?len,再乘上前kkk個數的組合
即:(k+1)!?∑i=1n?k?1(k+1)n?k?1?len?(n?k?len)(k+1)!*\sum_{i=1}^{n-k-1}(k+1)^{n-k-1-len}*(n-k-len)(k+1)!?∑i=1n?k?1?(k+1)n?k?1?len?(n?k?len)
-
這時的反序表有且僅有一個位置,其si>1s_i>1si?>1(嚴格大于)
e.g. 最后序列為2 3 1 4 5,反序表為0 0 2 0 0
相當于原始si>k+1s_i>k+1si?>k+1,這個iii同樣有范圍限制,為[k+3,n][k+3,n][k+3,n]
對于iii其選擇方案為i?1?(k+2)+1=i?k?2i-1-(k+2)+1=i-k-2i?1?(k+2)+1=i?k?2
即:∑i=k+3n∏j=1n[j≠i](min?(j?1,k)+1)?[j=i](i?k?1)\sum_{i=k+3}^n\prod_{j=1}^n[j≠i]\big(\min(j-1,k)+1\big)·[j=i](i-k-1)∑i=k+3n?∏j=1n?[j?=i](min(j?1,k)+1)?[j=i](i?k?1)
-
code
#include <cstdio> #include <iostream> using namespace std; #define maxn 5005 #define int long long int T, n, k, mod, fac; int inv[maxn], mi[maxn];int qkpow( int x, int y ) {int ans = 1;while( y ) {if( y & 1 ) ans = ans * x % mod;x = x * x % mod;y >>= 1;}return ans; }signed main() {scanf( "%lld", &T );while( T -- ) {scanf( "%lld %lld %lld", &n, &k, &mod );fac = inv[1] = mi[0] = 1;if( k >= n ) {for( int i = 1;i <= n;i ++ )fac = fac * i % mod;printf( "%lld\n", fac );continue;}for( int i = 1;i <= k + 1;i ++ )fac = fac * i % mod;for( int i = 2;i <= k + 1;i ++ )inv[i] = ( mod - mod / i ) * inv[mod % i] % mod;for( int i = 1;i <= n;i ++ ) mi[i] = mi[i - 1] * ( k + 1 ) % mod;int ans = fac * mi[n - k - 1] % mod;for( int i = 1;i <= n - k - 1;i ++ )ans = ( ans + fac * ( n - k - i ) % mod * mi[n - k - 1 - i] ) % mod;int mul = 1;for( int i = 1;i <= n;i ++ )mul = mul * ( min( i - 1, k ) + 1 ) % mod;for( int i = k + 3;i <= n;i ++ )ans = ( ans + mul * inv[min( i - 1, k ) + 1] % mod * ( i - k - 2 ) ) % mod;printf( "%lld\n", ans );}return 0; }總結
以上是生活随笔為你收集整理的[2021-09-09 T2] 就差⼀点——冒泡排序和反序表之间不为人知的秘密的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图片边框怎么去掉(wps图片边框怎么去掉
- 下一篇: iTunes store里显示电影商店不