1. 程式人生 > >『8.21 模擬賽』冒泡排序 II

『8.21 模擬賽』冒泡排序 II

最終 inline 集中 為什麽 cstring 子序列 乘法 cnblogs space

題目描述

前一天的冒泡排序對rsw來說太簡單了,所以又有了冒泡排序2,給定n,k,q,問:有多少個不同的1~n的排列,能夠使得,冒泡排序k趟後,得到一個幾乎正確的序列。

一個幾乎正確的序列指的是:它的最長上升子序列的長度至少是n-1。

技術分享圖片




解題思路

思路就是沒有思路。。。。

昨天剛剛做過一道冒泡排序的題,有一個結論在這裏

每個位置上的數最遠是由前面的第k個位置轉移過來的,並且題目中要求最長上升子序列最短是n-1的長度,所以只有兩種情況:

1) 最終是從小到大排序好的

2)最終是從小到大排序好的序列中選擇一段向前循環滾動一個位置或者向後滾動一個位置,這樣就有一個數在它不應該在的位置

我們既然正著推不好推,那我們就反著推唄,從每個幾乎正確的序列轉移到最初的情況。

技術分享圖片

比如這張圖片,k=3。3,4,5,6向前滾動了一次,下面代表著每個位置的數的位置有集中選擇,顯然,在3前面,每個數都有k+1種選擇,然鵝到了3,我們只能把它放在9那裏,為什麽呢? 如果3在8上,那排完序後3一定跑到6哪裏去了,對吧?剩下的數,由於區間長度不過了,他們只能找位置放在最後的地方了,所以是3,2,1。

那麽,除了3和最後k個沒地方往後放的數以外,所有數都有k+1種放的方法,所以我們先有\((k+1)^\left(n-k-1\right)\)種放的方法,後面的幾個數沒地方放了,那答案就是\(k!\),由乘法原理可以得到這樣的答案總數為\((k+1)^\left(n-k-1\right)×k!\)

。假設區間長為i,這樣下來,總共有n-k-i+1個地方可以選做區間的開頭,所以總答案就是\((k+1)^\left(n-k-1\right)×k!×(n-k-i+1)\)

向右平移的方法跟這個差不多,按照這個方法也可以推出來。




代碼

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
ll p[100];
ll n,k,q;
int main(){
    scanf("%lld%lld%lld",&n,&k,&q);
    p[0]=1;
    for(register ll i=1;i<100;i++)p[i]=(p[i-1]*(k+1))%q;
    k=min(k,n);
    ll ans=p[n-k];
    for(register ll i=2;i+k<=n;i++){
        ans=(ans+(ll)(n-k+1-i)*(ll)p[n-k+1-i])%q;
    }
    for(register ll i=3;i+k<=n;i++){
        ans=(ans+(ll)(n-k+1-i)*(ll)p[n-k-1])%q;
    }
    for(register ll i=1;i<=k;i++){
        ans=(ans*i)%q;
    }
    cout<<ans<<endl;
}

『8.21 模擬賽』冒泡排序 II