luogu P1521 题解

一开始可以把排逆序对看成从小到大插入元素, 我们令 \(a_i\) 表示插入元素 \(i\) 所产生的逆序对数量。

可以推出

\[\begin{cases} a_1 < 1\\ a_2 < 2\\ a_3 < 3\\ \dots \\ a_i < i\\ a_n < n\\ a_1 + a_2 + \dots a_n = k \end{cases} \]

考虑容斥

我们令 \(sum\) 表示选出的不满足的位置逆序的的和, \(t\) 表示选出了多少个数不合法, 可以发现, 通过插板法对答案的贡献为 \((-1)^{t} \cdot C_{k + n - 1 - sum}^{n - 1}\)

显然, \(\frac{t \cdot (t + 1)}{2} \le sum, t \le \sqrt{2sum}\)

考虑变换一下要求的东西

对于以上信息, 只需要求出对于两两不同的数组 \((b_1, b_2 \dots b_k), 1 \le b_1 < b_2 < \dots < b_k \le n, \sum b_i = sum\), 的方案数为 \(x\), 则贡献是 \((-1)^k \cdot C_{k + n - 1 - sum}^{n - 1}\), 这种发案相当于是 \(b_1, b_2, \dots b_k\) 都不合法。

考虑 DP, 状态 \(f_{i, j}\) 表示选了 \(i\) 个数, \(a_i\) 的和为 \(sum\) 时的方案数。

若选的元素都 $ > 1$, 则可以把选的数都 -1 的方案再全部 +1 即可回到这个方案, 所以答案为 \(f_{i, j - i}\), 若答案里有可能包含 \(n + 1\), 方案数为 \(f_{i - 1, j - (n + 1)}\), 需要减去这一部分的代价。

若选了元素存在 \(1\), 只需要先把 \(1\) 删到, 即 \(f_{i - 1, j - 1}\), 这里面还不能包含元素 \(1\), 所以答案应为 \(f_{i - 1, j - 1 - (i - 1)}\)

所以 \(f_{i, j} = f_{i - 1, j - i} + f_{i, j - i} - f_{i - 1, j - (n + 1)}\)

时间复杂度 \(O(k \sqrt{k} + n)\)

#include<bits/stdc++.h>

using namespace std;

const int N = 2e5 + 5, mod = 1e9 + 7;

int n, k, dp[505][N], g[N], f[N], inv[N], ans;

int C(int n, int m){
  return 1ll * f[n] * g[m] % mod * g[n - m] % mod;
}

int main(){
  cin >> n >> k;
  f[0] = g[0] = inv[1] = 1;
  for(int i = 1; i <= 200000; ++i){
    f[i] = 1ll * f[i - 1] * i % mod;
    if(i > 1){
      inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;
    }
    g[i] = 1ll * g[i - 1] * inv[i] % mod;
  }
  dp[0][0] = 1;
  ans = C(k + n - 1, n - 1);
  for(int i = 1; i <= 500; ++i){
    for(int j = 0; j <= k; ++j){
      if(j - i >= 0){
        dp[i][j] = (dp[i - 1][j - i] + dp[i][j - i]) % mod;
      }
      if(j - (n + 1) >= 0){
        dp[i][j] = (dp[i][j] - dp[i - 1][j - (n + 1)] + mod) % mod;
      }
      if(k + n - 1 - j >= n - 1){
        ans = (ans + 1ll * (i % 2 ? -1 : 1) * dp[i][j] * C(k + n - 1 - j, n - 1) % mod + mod) % mod;
      }
    }
  }
  cout << ans;
  return 0;
}

posted @ 2024-07-30 12:11  liuyichen  阅读(15)  评论(0)    收藏  举报