【题解】P7322 「PMOI-4」排列变换
P7322 「PMOI-4」排列变换
题意
给定常数 \(k\)。对于一个长度为 \(n\) 的排列 \(a\),定义
对于一个长度为 \(n\) 的序列 \(a\),定义其权值 \(w(a)\) 为 \(a\) 中不同的数的个数。
现在,\(\text{ducati}\) 想知道,对于所有长度为 \(n\) 的排列 \(p\),它们的 \(w(f(p))\) 之和。
题解
知识点:组合数。
启发:
- 一种贡献转化方式。
很好的题目,提供一种简单做法。
直接做是困难的,先思考 \(f(a)\) 的本质。
可以发现 \(f(a)\) 相当于模拟一个长度为 \(k\) 的滑动窗口滑过排列 \(a\),并依次记录每个位置下窗口内的最大值。
而 \(w(f(a))\) 也就是最大值的突变次数再加上 \(1\)。
接着转变角度,考虑 \(1\sim n\) 每个数会在多少个排列中引起突变。
假设这个数为 \(i\),那么无非就两种情况会引起突变:
-
窗口刚移入 \(i\),\(i\) 变成最大值。
-
窗口刚移出 \(i\),\(i\) 不再是最大值。
不过这还不够,仔细思考,发现这样设计的话,贡献会重复,考虑加紧限制:
-
窗口刚移入 \(i\),最大值增大为 \(i\)。
-
窗口刚移出 \(i\),最大值不再为 \(i\),且最大值变小。
仔细分析,发现这样设计是不重不漏的,下一步考虑计算。
观察到,第二种情况和第一种本质是相同的,无非就是反过来了,那我们直接用第一种情况的排列数乘以二就可以得到总贡献。
首先,有 \((n-k)\) 个位置能让 \(i\) 处于窗口最左端且窗口还能继续滑动。
其次,要让窗口移出后最大值变小,则 \(i\) 后面的 \(k\) 个数都得比 \(i\) 小,也就是从 \((i-1)\) 个数中选 \(k\) 个组成排列。
其他的数就无所谓了,任意排列均可。
结合组合数,可以推出如下式子:
化简一下得到:
则最终答案就是:
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,l,r) for(int i=(r);i>=(l);--i)
#define pr pair<int,int>
#define fi first
#define se second
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
#define bg(x) (x).begin()
#define ed(x) (x).end()
#define N 502504
#define int long long
const int mod=998244353;
namespace comb{
int fac[N];
inline int inv(int a){
int b=mod-2,ans=1;
while(b){
if(b&1){
ans=ans*a%mod;
}
a=a*a%mod;
b>>=1;
}
return ans;
}
inline int C(int n,int m){
if(n<m){
return 0;
}
return fac[n]*inv(fac[m])%mod*inv(fac[n-m])%mod;
}
inline void init(int lim){
fac[0]=1;
rep(i,1,lim){
fac[i]=fac[i-1]*i%mod;
}
}
};
using comb::C,comb::fac;
int n,k;
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
comb::init(5e5);
cin>>n>>k;
int ans=fac[n];
rep(i,1,n){
ans=(ans+2*fac[n-k]%mod*fac[k]%mod*C(i-1,k)%mod)%mod;
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号