[2018 ICPC 沈阳 C] Insertion Sort

https://codeforces.ml/gym/101955/problem/C
题意:
给你三个正整数\(n\)\(k\)\(q\)\((1 \leq n, k \leq 50, 10^8 \leq q \leq 10^9)\),其中\(q\)为素数。我们约定\(1\)\(n\)的一种排列是几乎已排序的定义如下:其最长上升子序列的长度至少为\(n - 1\)。现在我们可以将排列的前\(k\)个数按序排列,问有多少种序列能成为几乎已排序的,将答案\(mod\) \(q\)输出。

思路:
首先将序列变成\(1\)~\(n\)的顺序排列,然后考虑对这个排列进行变形。

我们考虑前\(k\)个是\(1\)~\(k\)的全排列 ,那么后面数字都会比前面大,我们只要保证其最长上升子序列的长度至少为\(n-k+1\)即可。这一步我们可以选择后面任意一个数字进行插排即可,但是这一步会重复计数。我们先算出\(\dbinom{n-k}{1}*\dbinom{n-k}{1}\),然后考虑重复的情况。首先每个数都会进行一次顺序排列,所以要减去\(n - k - 1\)。然后只有一组相邻逆序对的情况也是会重复计数的。例如\(435\),我们取出3和4的时候都能达到这种情况,其余情况皆不重复。共有\(n-k-1\)个这种序列,所以也要减去。

接下来考虑前面并不是1~k的全排列的情况,我们发现可以用\(k+1\)去代替其中任意一个数字,然后把这个被替代的数字在后面进行插排,都是合法的。\(k+2\) ~ \(n\)之间的数字只能和\(k\)进行替换,并且只能放在k这个位置上。这样子的方案数是\(\dbinom{k}{1}*\dbinom{n-k}{1} + n - k - 1\)

答案总体要乘以\(k!\)。注意题目并没有给出\(k \leq n\)的条件,由于书写习惯的不同,这步可能需要特殊处理。

赛时\(de\)了两小时的\(bug\),发现没有注意\(k>n\)的情况,我的写法是会有影响的。噫!好!老演员了!

#include<bits/stdc++.h>
using namespace std;

const int N = 50 + 7;

typedef long long ll;

ll n, k, mod;
int cas;

ll calc(ll n, ll k) {
    ll ak = 1;
    for (ll i = 2; i <= min(k, n); ++i) {
        ak = ak * i % mod;
    }
    if (n - k == 1) ak = ak * n % mod;
    if (n - k <= 1) return ak;
    ll ans = 0;
    ans = ans + ak * ((n - k) * (n - k) - 2 * (n - k) + 2) % mod;
    ans = ans + (ak * (k * (n - k) + (n - k - 1))) % mod;
    ans %= mod;
    return ans;
}

void solve() {
    cin >> n >> k >> mod;
    cout << "Case #" << ++cas << ": ";
    cout << calc(n, k) << endl;
}

int main () {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) solve();
    return 0;
}
posted @ 2021-03-27 23:09  stff577  阅读(93)  评论(0)    收藏  举报