好神仙!
\(\color{#5bc9}\text{提醒,本文有大量没有推到过程的式子,所以读者可以自己遮住先推一下}\)
Inscription:
有一个长度为 \(k\) 的窗口,在一个长度为 \(n\) 的序列 \(a\) 上滑动,请问滑动窗口中的数的 \(\min\) 共有多少种值。
Solution:
接下来我们考虑之后什么情况才会让答案 \(+1\)。
既然每次向右移动 \(1\) 的长度,那么只有可能删除的数或者新加入的数是最小值答案才有可能 \(+1\)。
对于最左边的数是窗口滑动之前是窗口最小值,进行一波推式子之后(此处省略一万行),可以发现它对答案的贡献有:
解释一下:
其中从左往右,求和符合是枚举这个最小值的大小,组合数是令窗口中剩余的 \(k−1\) 个数大于最小值的值的个数。
两个阶乘分别是窗口外面的数任意排列和窗口内部除去最小值外任意排列。
最后一个 \(n−k\) 是计算排列中窗口可以在的位置有 \(n−k\) 个。
同理新加入的数的的贡献就为:
\[s2=\sum^{n-k}_{i = 1} C^{k}_{n-i} \times (n-k-1)! \times k! \times (n-k) \]但是未免有左右两边都是最小的情况,所以我们要减去。
所以需要减掉的部分为:
\[s3=\sum^{n-k}_{i = 1}C^{k-1}_{n-i} \times (n - k - 1)! \times (k - 1)! \times (n-k) \times (i-1) \]\(i−1\) 是最右边小于最左边的个数。
\(\color{violet} \text{注意:最后还需要加上每个排列都缺少的 1 答案,也就是总答案加上 n!}\)。
但是我不甘于 \(O(n-k)\) 的做法(除了预处理),于是就有了下面的事情。
我们来化简 \(s1,s2,s3\)(化简过程略,读者可以自己遮住先推一下)。
\[s1 = \sum^{n-k}_{i=1} \dfrac{n-k}{(n-k-i+1)!} \times (n-k)! \times (n-i)! \]\[s2 = \sum^{n-k}_{i=1} \dfrac{1}{(n-k-i)!} \times (n-k)! \times (n-i)! \]\[s3 = \sum^{n-k}_{i=1} \dfrac{i-1}{(n-k-i+1)!} \times (n-k)! \times (n-i)! \]然后呢,就有一个规律,那就是 \(s1 - s3 = s2\)。
所以答案从 \(s1 + s2 - s3 + n!\) 变成了 \(2 \times s2 + n!\)。
但是,我们还不满足。
\(s2\) 仍然可以化简(读者可以自己遮住先推一下)。
\[s2 = \dfrac{(n+1)!}{k+1} - n! \]所以把上面这个式子带入求答案的式子。
所以:
\[\dfrac{2 \times (n+1)!}{k+1} - n! \]所以完结撒花!!
最后贴上代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 998244353;
int qpow(int a,int b)
{
int res = 1;
while(b)
{
if(b & 1)
{
res = res * a % mod;
}
b >>= 1;
a = a * a % mod;
}
return res;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,k;
cin >> n >> k;
int fac = 1;
for(int i = 1;i <= n;i++)
{
fac = fac * i % mod;
}
cout << (2 * (fac * (n + 1) % mod) * qpow(k + 1,mod - 2) % mod - fac + mod) % mod;
return 0;
}
标签:int,s2,sum,P7322,times,s3,dfrac
From: https://www.cnblogs.com/Carousel/p/17686000.html