P7322 「PMOI-4」排列变换

2022/9/10 6:24:42

本文主要是介绍P7322 「PMOI-4」排列变换,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

P7322 「PMOI-4」排列变换

题目大意

给定常数 \(k\)。对于一个长度为 \(n\) 的排列 \(a\),定义

\[f(a)=\{\max_{1 \le i \le k} \{a_i\},\max_{2 \le i \le k+1} \{a_i\},\cdots,\max_{n-k+1 \le i \le n} \{a_i\}\} \]

对于一个长度为 \(n\) 的序列 \(a\),定义其权值 \(w(a)\) 为 \(a\) 中不同的数的个数。

现在,\(\text{ducati}\) 想知道,对于所有长度为 \(n\) 的排列 \(p\),它们的 \(w(f(p))\) 之和。

分析

就是给定一个长度为 kk 的滑动窗口在长度为 n 的排列上滑,问滑动窗口中的 max 变化了多少次。(滑动窗口不懂的可以理解成序列中连续 k个数。这里之所以不写不同数的个数而是变化次数,是因为题目求的是排列,而排列满足所有数各不相同,所以变化次数加 1 就等于不同 max 个数)

可以发现,因为枚举全排列中的每一个 i 都可以变成 n-i+1n! 个排列仍然完整),所以我们计算答案,求 minmax 其实是等价的。

因为向右滑动 1 长度的情况下,只有窗口最左的数会出窗口,而右边紧邻的那个数会进入窗口,所以我们实际上只需要对于每个窗口看最左边的数是否是窗口滑动之前的最小值,以及新加入的最右边的数是否是窗口滑动之后新的最小值即可。

对于最左边的数是窗口滑动之前是窗口最小值,进行一波推式子之后,可以发现它对答案的贡献有:

\[\sum_{i=1}^{n}C(n-i,k-1)(n-k)!(k-1)!(n-k) \]

其中从左往右,求和符合是枚举这个最小值的大小,组合数是令窗口中剩余的 k-1 个数大于最小值的值的个数,两个阶乘分别是窗口外面的数任意排列和窗口内部除去最小值外任意排列,最后一个 n-k 是计算排列中窗口可以在的位置有 n-k 个。(其实总窗口有 n-k+1 个,就是最左边那个无法移动了)

然后对于新进入的最右边的数是新的窗口最小值,式子是可以类比的:

\[\sum_{i=1}^{n}C(n-i,k)(n-k-1)!k!(n-k) \]

含义是类似的,只不过计算的是新加进去最右边那个数。

当然,只将这两部分加起来还有一些问题,实际上我们还会算重一部分,既满足最左边是窗口最小值,又满足新加入的是窗口的新最小值(且小于最左边的,或者你可以强制令最右边的是最小的,最左边的小于最右边的,实际上两种方法的式子是一样的),这部分答案需要减掉:

\[\sum_{i=1}^{n}C(n-i,k-1)(n-k-1)!(k-1)!(n-k)(i-1) \]

前面同样是类似的,最后的 i-1 是最右边小于最左边的个数,或者你也可以理解成是最左边小于最右边的个数。

注意最后还需要加上每个排列都缺少的 1 答案,也就是总答案加上 n!,因为我们最开始忽略了这部分。

Ac_code

#include<bits/stdc++.h>
#define ios ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
using namespace std;

const int N = 5e5 + 10,mod = 998244353;

template<int T>
struct ModInt {
    const static int mod = T;
    int x;
    ModInt(int x = 0) : x(x % mod) {}
    int val() { return x; }
    ModInt operator + (const ModInt &a) const { int x0 = x + a.x; if (x0 >= mod) x0 -= mod; if (x0 < 0) x0 += mod; return ModInt(x0); }
    ModInt operator - (const ModInt &a) const { int x0 = x - a.x; if (x0 >= mod) x0 -= mod; if (x0 < 0) x0 += mod; return ModInt(x0); }
    ModInt operator * (const ModInt &a) const { return ModInt(1LL * x * a.x % mod); }
    ModInt operator / (const ModInt &a) const { return *this * a.inv(); }
    void operator += (const ModInt &a) { x += a.x; if (x >= mod) x -= mod; if (x < 0) x += mod;}
    void operator -= (const ModInt &a) { x -= a.x; if (x < 0) x += mod; if (x >= mod) x -= mod;}
    void operator *= (const ModInt &a) { x = 1LL * x * a.x % mod; }
    void operator /= (const ModInt &a) { *this = *this / a; }
    friend ostream &operator<<(ostream &os, const ModInt &a) { return os << a.x;}
    
    ModInt pow(int n) const {
        ModInt res(1), mul(x);
        while(n){
            if (n & 1) res *= mul;
            mul *= mul;
            n >>= 1;
        }
        return res;
    }
    
    ModInt inv() const {
        int a = x, b = mod, u = 1, v = 0;
        while (b) {
            int t = a / b;
            a -= t * b; swap(a, b);
            u -= t * v; swap(u, v);
        }
        if (u < 0) u += mod;
        return u;
    }
    
};
typedef ModInt<mod> mint;

mint fact[N],infact[N];
int n,k;

void init()
{
    fact[0] = infact[0] = 1;
    for(int i=1;i<=n;i++) fact[i] = fact[i-1]*mint(i);
    infact[n] = fact[n].inv();
    for(int i=n-1;i;i--) infact[i] = infact[i+1]*mint(i+1);
}

mint C(int a,int b)
{
    return fact[a]*infact[b]*infact[a-b];
}

int main()
{
    ios;
    cin>>n>>k;
    init();
    mint ans = fact[n];
    for(int i=1;i<=n-k;i++)
    {
        ans += ((C(n-i,k-1)*fact[n-k])*fact[k-1])*mint(n-k);
        ans += ((C(n-i,k)*fact[n-k-1])*fact[k])*mint(n-k);
        ans -= (((C(n-i,k-1)*fact[n-k-1])*fact[k-1])*mint(n-k))*mint(i-1);
    }
    cout<<ans<<'\n';
    return 0;
}


这篇关于P7322 「PMOI-4」排列变换的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程