题解 SP64 【PERMUT1 - Permutations】

rui_er

2020-08-25 18:11:59

Solution

本题是三倍经验 [Link1](/problem/P2513) [Link2](/problem/P1521),不知道为啥这几题难度评分不一样,区别只有多组数据和模数。 好了,下面进入正题。 --- 题意简述:多组数据,给定 $n$,求 $1\sim n$ 的所有排列中逆序对个数恰有 $k$ 个的情况。 由于有多组数据,因此考虑预处理出所有情况后进行 $O(1)$ 查询,~~虽然这题数据范围较小好像也不用。~~ 怎么进行预处理呢?考虑进行动态规划。 设计状态 $dp_{i,j}$ 表示对于 $1\sim i$ 的所有排列,恰有 $j$ 个逆序对的情况总数。 考虑如何进行转移。我们在 $1\sim i-1$ 的一个排列,添加一个数 $i$,此时我们可以使逆序对个数增加 $1,2,\cdots,i-1$ 个。因此,我们可以得到转移方程: $$dp_{i,j}=\sum_{k=0}^{i-1}dp_{i-1,j-k}$$ 此时,根据我们状态的定义,$dp_{n,k}$ 即为所求。 --- 这是我们注意到一个问题:这个转移是 $O(n^3)$ 的,无法通过上面两道双倍经验,因此需要优化。 看到我们最后一维要加的是一个区间内的 $dp$ 值,容易想到通过维护前缀和来优化时间。我们采取循环中滚动求前缀和的方式。 代码中的前缀和开的是数组,也有只用一个变量的做法(其实并不难),~~因为我懒~~,这里就不写了。 另外,由于数量可能很大,需要开 long long。 --- 代码: ```cpp //By: Luogu@rui_er(122461) #include <bits/stdc++.h> using namespace std; typedef long long ll; const ll N = 1005; ll T, n, m; ll dp[N][N] = {{0}, {1}}, s[N]; void dpInit() { for(ll i=2;i<=1000;i++) { memset(s, 0, sizeof(s)); for(ll j=0;j<=1000;j++) { s[j] = dp[i-1][j]; if(j) s[j] += s[j-1]; dp[i][j] = s[j]; ll _ = j - i + 1; if(_ >= 0) s[j] -= dp[i-1][_]; } } } int main() { dpInit(); scanf("%lld", &T); while(T--) { scanf("%lld%lld", &n, &m); printf("%lld\n", dp[n][m]); } return 0; } ```