P10254 口吃 题解

题意:一个长度为 \(n\) 的排列合法当且仅当逆序对数为 \(k\),且一个排列的价值定义为 \(\sum a_i \times i\)。给定 \(n\)\(k\),求出所有合法排列的价值和。

容易想到 \(\text{dp}\)

会发现如果直接 \(\text{dp}\) 的话,每一个位置的数的贡献是有后效性的(因为后面会不断加入数,这个数的下标 \(i\) 也会发生变化,于是价值 \(i \times a_i\) 也会跟着变化)。所以我们考虑把贡献拆开计算。具体地,对于每一个 \(i\),我们可以把 \(a_i \times i\) 拆成 \(sum0 \times a_i + sum1 \times a_i\),其中 \(sum0/1\) 分别表示在 \(1\)\(i\) 中大于等于 / 小于 \(a_i\) 的数的个数。我们可以分别求出这两个的答案,最终相加即可。(其实这两种贡献本质完全一样)

先考虑 \(sum0\) 的贡献。假设我们从 \(n\) 开始降序选数,设 \(f_{i,j}\) 表示选完了 \(i\) 个数,逆序对数为 \(j\) 的方案数,\(g_{i,j}\) 表示选完了 \(i\) 个数,逆序对数为 \(j\) 的总价值和,\(P\) 表示前 \(i-1\) 个数的逆序对数最少是多少,那么有:

\[P=\max(0,j-(i-1)) \]

\[f_{i,j}=\sum_{p=P}^{j} f_{i-1,p} \]

\[g_{i,j}=\sum_{p=P}^{j} g_{i-1,p} +f_{i-1,p} \times (n-i+1) \times (j - p + 1) \]

前两个式子比较好理解。第三个转移方程中的 \((n-i+1)\) 表示当前选到第 \(i\) 个数的值是多少,\((j-p+1)\) 表示当前这个数在序列中是第几个数。因为后面的数加进来的数一定比第 \(i\) 个数小了,且之前加进来的数一定比第 \(i\) 个数大(降序选),所以 \(sum0=j-p+1\)。(这就体现出了拆贡献的好处,如果不拆开算的话后面加进来的数不好处理),\(sum2\) 的贡献同理。

这个 \(\text{dp}\) 的复杂度是 \(O(n^2 k)\) 的,但是每一个 \(\sum\) 其实都可以用前缀和优化掉,所以时间复杂度 \(O(nk)\)。另外 \(\text{dp}\) 数组的第一维可以滚掉,空间复杂度 \(O(k)\)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define now (i & 1)
#define p max(0ll,j - (i - 1))
const int mod = 998244353;
const int MAXN = 3e2 + 10;
const int MAXK = 1e5 + 10;
int n,k,f[3][MAXK],g[3][MAXK],sumf[MAXK],sumg[MAXK];
int h[3][MAXK],sumh[MAXK],sumF[MAXK];
signed main() {
	cin >> n >> k;
	for(int i = 0;i <= k;i++) sumf[i] = 1;
	for(int i = 1;i <= n;i++) {
		for(int j = 0;j <= k;j++)
			f[now][j] = (sumf[j] - (p ? sumf[p - 1] : 0) + mod) % mod,
			g[now][j] = (sumg[j] - (p ? sumg[p - 1] : 0) + mod) % mod,
			g[now][j] = (g[now][j] + ((n - i + 1) * (j + 1) % mod * f[now][j] % mod)) % mod,
			g[now][j] = (g[now][j] - (n - i + 1) * ((sumF[j] - (p ? sumF[p - 1] : 0) + mod) % mod) % mod + mod) % mod,
			h[now][j] = (sumh[j] - (p ? sumh[p - 1] : 0) + mod) % mod,
			h[now][j] = (h[now][j] + (i * (i - j - 1) % mod * f[now][j] % mod) + mod) % mod,
			h[now][j] = (h[now][j] + i * ((sumF[j] - (p ? sumF[p - 1] : 0) + mod) % mod) % mod + mod) % mod;
		for(int j = 0;j <= k;j++)
			sumf[j] = ((j ? sumf[j - 1] : 0) + f[now][j]) % mod,
			sumg[j] = ((j ? sumg[j - 1] : 0) + g[now][j]) % mod,
			sumh[j] = ((j ? sumh[j - 1] : 0) + h[now][j]) % mod,
			sumF[j] = ((j ? sumF[j - 1] : 0) + f[now][j] * j % mod) % mod;
	}
	cout << (g[n & 1][k] + h[n & 1][k]) % mod;
	return 0;
} 
posted @ 2024-03-20 20:17  Creeper_l  阅读(10)  评论(0编辑  收藏  举报