zoukankan      html  css  js  c++  java
  • 【做题】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;
    }
    

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

  • 相关阅读:
    Nginx配置文件
    SSM三层模型之间的参数传递
    Junit4用法
    常量类的设计
    初识Oracle
    sss
    sss
    sss
    sss
    sss
  • 原文地址:https://www.cnblogs.com/cly-none/p/9297850.html
Copyright © 2011-2022 走看看