欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

[2021-09-09 T2] 就差⼀点——冒泡排序和反序表之间不为人知的秘密

发布时间:2023/12/3 56 豆豆
生活随笔 收集整理的这篇文章主要介绍了 [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-1n1的上升子序列

小明想知道,对于给定的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+71n,k,T5000,108mod109+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=1j1dpi1,k+jdpi,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=1i1[pj>pi]

  • 即反序表的第iii位上的数值表示:在原排列中,第iii位以前的比第iii位值大的个数

e.g. 原序列3 2 4 1 5,反序表为0 1 0 3 0

反序表具有很多非常好的性质

  • 显然对于iiisi<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)isimin(i1,k)

    将这些值域限制乘起来就是不同的反序表个数,也就是不同的排列个数

    即:∏i=1n(min⁡(i−1,k)+1)\prod_{i=1}^n\Big(\min(i-1,k)+1\Big)i=1n(min(i1,k)+1)

  • 考虑冒泡排序后,最后的序列是一个长为n−1n-1n1的上升子序列(只差一点)

    • 这时的反序表形如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+1iii是有限制的,仅为[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=nk1

      (不要忘记si<is_i<isi<i的约束)

      考虑枚举这段111的长度lenlenlen

      这段长度的选择方案相当于在总长为n−k−1n-k-1nk1中摆下lenlenlen的放置方案,显然为n−k−1−len+1=n−k−lenn-k-1-len+1=n-k-lennk1len+1=nklen

      剩下的n−k−1−lenn-k-1-lennk1len个数反序表都不超过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)nk1len,再乘上前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=1nk1(k+1)nk1len(nklen)

    • 这时的反序表有且仅有一个位置,其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-2i1(k+2)+1=ik2

      即:∑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+3nj=1n[j=i](min(j1,k)+1)[j=i](ik1)

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] 就差⼀点——冒泡排序和反序表之间不为人知的秘密的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。