Maximum Element(dp)

CF886E

模拟赛出来这道,自己写出来了,虽然花了挺久的,开心!开心!


首先看到 \(n\leq10^6\) 不是线性 dp 就是数学。


数往后 \(k\) 个数小于自己实在是太蠢了,不好计数,但是反过来,输出正确答案的情况十分优美。

输出正确的情况大致可以抽象为这样一个形式,对于一个原序列的上升子序列 \(S\)\(S\) 的相邻两项之间的数小于它们,子序列最后一项后面的数也小于它,并且子序列的相邻两项在原列的位置之差不超过 \(k\)

现在随便胡一个二维状态应该可以做到 \(O(n^3)\) 或者 \(O(n^2)\),可是 \(n\leq 10^6\),考虑设一维状态。

由于题目要求是一个序列,所以我们不关心一个数实际的大小,我们只关心相对大小,我们可以设一个状态 \(f_i\) 表示对于一个 \(n = i\) 的数列, 我们钦定最后一个数是 \(n\),满足上述条件的方案数。转移可以枚举一个转移点,然后把新增的数插板插进原数列中,即:

\[f_i = \sum_{j = i - k}^{i - 1} f_j \binom{i - 2}{j - 1}(i - j - 1)! \]

简单拆一下:

\[f_i = \sum_{j = i - k}^{i - 1} f_j \dfrac{(i - 2)!}{(j - 1)!(i - j - 1) !}(i - j - 1)! \]

\[f_i = (i - 2)!\sum_{j = i - k}^{i - 1} \dfrac{f_j}{(j - 1)!} \]

前缀和优化转移即可。

答案只需要枚举 \(n\) 在哪个位置,然后乘上 \(n\) 后面的数带来的贡献,即:

\[ans = n! - \sum_{i = 1}^{n} f_i \binom{n - 1}{i - 1}(n - i) \]

我真聪明。

code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e6 + 10;
const int mod = 1e9 + 7;
ll Pow(ll a, ll b, ll mod) {
	ll res = 1;
	while(b) {
		if(b & 1) res = res * a % mod;
		a = a * a % mod, b >>= 1;
	}
	return res;
}
int f[maxn];
int pre[maxn];
int fac[maxn];
int ifac[maxn];
int C(int n, int m) {
	return 1ll * fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
int n, k;
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> k;
	ifac[0] = fac[0] = 1;
	for(int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % mod; 
	ifac[n] = Pow(fac[n], mod - 2, mod);
	for(int i = n - 1; i; i--) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mod;
	f[1] = 1;
	pre[1] = 1;
	for(int i = 2; i <= n; i++) {
		int l = max(1, i - k);
		f[i] = 1ll * fac[i - 2] * ((1ll * pre[i - 1] - pre[l - 1] + mod) % mod) % mod;
		pre[i] = (pre[i - 1] + 1ll * f[i] * ifac[i - 1] % mod) % mod;
	}
	int ans = 0;
	for(int i = n; i >= 1; i--) {
		ans = ans + 1ll * f[i] * fac[n - i] % mod * C(n - 1, i - 1) % mod;
		ans %= mod;
	}
	cout << (fac[n] - ans + mod) % mod;
}
posted @ 2023-09-15 07:45  N2MENT  阅读(17)  评论(0)    收藏  举报