做题CF285E. Positions in Permutations——dp+容斥

Posted cly-none

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了做题CF285E. Positions in Permutations——dp+容斥相关的知识,希望对你有一定的参考价值。

题意:求所有长度为(n)的排列(p)中,有多少个满足:对于所有(i ,(1 leq i leq n)),其中恰好有(k)个满足(|p_i - i| = 1)。答案对(10^9 + 7)取模。

(n leq 10^3)

首先,让我们考虑这个类似反演的结论:

对于(F(n))(f(n)),则满足

[F(n) = sum_{k geq n}{{k}choose{n}}f(k) iff f(n) = sum_{k geq n}(-1)^{k-n}{{k}choose{n}}F(k)]

对于充分性,我们有
[ egin{aligned} & sum_{k geq n}(-1)^{k-n}{{k}choose{n}}F(k) \ = & sum_{k geq n}(-1)^{k-n}{{k}choose{n}} sum_{j geq k} {{j}choose{k}} f(j) = & sum_{j geq n} f(j) {{j}choose{n}} sum_{n leq k leq j} (-1)^{k-n} {{j-n}choose{k-n}} = & sum_{j geq n} f(j) {{j}choose{n}} sum_{0 leq k leq j-n} (-1)^k {{j-n}choose{k}} = & sum_{j geq n} f(j) {{j}choose{n}} (1-1)^{j-n} = & f(n)end{aligned} ]
而对于必要性,我们也能给出类似的证明。

观察这个结论,不难发现,我们平时使用的容斥就是这个结论求(f(0))时的特殊情况。而现在我们要求的是(f(k)),问题就变成了把所有(F(n))都求出来。

(F(n))的定义正对应了我们求至少有(k)个的情况总数时,重复统计所得到的结果。这使得它可以比较容易地求出。

我们考虑权值和位置表示为二分图的形式,那么问题就在于求二分图恰好有(k)个匹配的方案数。考虑dp。设dp[i,j,a,b]表示当前匹配到第(i)个权值和位置,已经有(j)个匹配,并且(a)(b)分别表示第(i)个位置与权值是否已经与编号更小的权值与位置匹配。通过枚举(a)(b),很容易能得到dp的转移。当然,最后的答案还要乘以一个阶乘。

时间复杂度(O(n^2))

#include <bits/stdc++.h>
using namespace std;
const int N = 1010, MOD = 1000000007;
typedef long long ll;
ll power(ll a,int b) {
  ll res = 1;
  while (b) {
    if (b&1) res = 1ll * res * a % MOD;
    a = a * a % MOD;
    b >>= 1;
  }
  return res;
}
int n,k;
ll dp[N][N][2][2],jc[N],inv[N],ans;
ll comb(int a,int b) {
  if (a < 0 || b < 0 || a < b)
    return 0;
  return jc[a] * inv[b] % MOD * inv[a-b] % MOD;
}
int main() {
  scanf("%d%d",&n,&k);
  dp[0][0][1][1] = 1;
  for (int i = 1 ; i <= n ; ++ i)
    for (int j = 0 ; j <= n ; ++ j) {
      (dp[i][j][0][0] = dp[i-1][j][0][0] + dp[i-1][j][1][0] + dp[i-1][j][0][1] + dp[i-1][j][1][1]) %= MOD;
      if (j >= 1) {
    (dp[i][j][1][0] = dp[i-1][j-1][0][0] + dp[i-1][j-1][1][0]) %= MOD;
    (dp[i][j][0][1] = dp[i-1][j-1][0][0] + dp[i-1][j-1][0][1]) %= MOD;
      }
      if (j >= 2)
    dp[i][j][1][1] = dp[i-1][j-2][0][0];
    }
  jc[0] = 1;
  for (int i = 1 ; i <= n ; ++ i)
    jc[i] = 1ll * i * jc[i-1] % MOD;
  inv[n] = power(jc[n],MOD-2);
  for (int i = n-1 ; i >= 0 ; -- i)
    inv[i] = 1ll * inv[i+1] * (i+1) % MOD;
  for (int j = k, p = 1 ; j <= n ; ++ j, p = -p) {
    ll v = 1ll * jc[n-j] * (dp[n][j][0][0] + dp[n][j][1][0] + dp[n][j][0][1] + dp[n][j][1][1]) % MOD;
    (ans += p * comb(j,k) * v % MOD) %= MOD;
  }
  ans = (ans % MOD + MOD) % MOD;
  cout << ans << endl;
  return 0;
}


小结:这种类似于反演的东西是很多组合问题的通用方法,希望自己能实现灵活的运用。


以上是关于做题CF285E. Positions in Permutations——dp+容斥的主要内容,如果未能解决你的问题,请参考以下文章

CF285E Positions in Permutations(dp+容斥)

Codeforces 285 E. Positions in Permutations

CodeForces - 285E: Positions in Permutations(DP+组合数+容斥)

题解CF#175(Div. 2) E-Positions in Permutations

CF285EPositions in Permutations

做题CF239E. k-d-sequence——线段树