【做题】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) \]

对于充分性,我们有

\[\begin{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;
}

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

posted @ 2018-07-12 09:28  莫名其妙的aaa  阅读(239)  评论(0编辑  收藏  举报