做题51NOD1518 稳定多米诺覆盖——容斥&dp

Posted cly-none

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了做题51NOD1518 稳定多米诺覆盖——容斥&dp相关的知识,希望对你有一定的参考价值。

题意:求有多少种方案,用多米诺骨牌覆盖一个(n imes m)的棋盘,满足任意一对相邻行和列都至少有一个骨牌横跨。对(10^9+7)取模。

(n,m leq 16)

首先,这个问题的约束比较复杂,直接dp需要较高的代价记录状态,不能通过本题。

然而,这个问题的约束可以被拆分为多个小约束(某条线被横跨),且小约束可以直接合并。这启发我们使用容斥。

这样,我们的dp计数就简化为了固定几条线不被跨越后任意覆盖。设(f_k)为恰有(k)条线不被跨越的方案数,(g_k)为我们计算出的固定了(k)条线后的覆盖方案数。那么,我们有
[ g_k = sum_{i geq k} {{i}choose{k}} f_i ]
由二项式反演可得
[ f_0 = sum_{k geq 0} (-1)^k g_k ]
剩下的问题就在于计算所有(g_k)了。我们不能枚举所有要被跨越的线,但是枚举一维之后,另一维就可以dp了。假设我们已经枚举了列上的线,令(dp_{i,j})表示前(i)行有(j)条线没有跨越的方案数,暴力转移。通过预处理能做到(O(n^3))。而考虑到状态中的(j)至于最后(-1)的指数有关,故可以省去。因此这个dp是(O(n^2))的。

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

#include <bits/stdc++.h>
using namespace std;
const int N = 20, MOD = (int)(1e9 + 7), MAX = 16;
int n,m,dp[2][1 << MAX],f[N][N],ans,g[N],rec[N];
typedef long long ll;
void doit(int wd) {
  memset(dp,0,sizeof dp);
  int p = 1, lim = (1 << wd) - 1;
  dp[0][(1 << wd)-1] = 1;
  for (int i = 1 ; i <= n ; ++ i) {
    for (int j = 1 ; j <= wd ; ++ j, p ^= 1) {
      memset(dp[p],0,sizeof dp[p]);
      for (int s = 0 ; s < (1 << wd) ; ++ s) {
        if (((s >> (wd-1))&1) == 0)
          (dp[p][(s << 1 | 1) & lim] += dp[p^1][s]) %= MOD;
        else {
          if ((!(s&1)) && j != 1) (dp[p][(s << 1 | 3) & lim] += dp[p^1][s]) %= MOD;
          (dp[p][(s << 1) & lim] += dp[p^1][s]) %= MOD;
        }
      }
    }
    f[i][wd] = dp[p^1][lim];
  }
}
void prework() {
  for (int i = 1 ; i <= m ; ++ i)
    doit(i);
}
vector<int> tmp;
int main() {
  n = m = 16;
  prework();
  while (scanf("%d%d",&n,&m) != EOF) {
    ans = 0;
    for (int s = (1 << m >> 1) ; s < (1 << m) ; ++ s) {
      tmp.clear();
      int las = 0;
      for (int i = 1 ; i <= m ; ++ i)
        if ((s >> (i-1))&1) tmp.push_back(i-las), las = i;
            for (int i = 1 ; i <= n ; ++ i) {
        rec[i] = 1;
        for (int j = 0 ; j < (int)tmp.size() ; ++ j)
          rec[i] = 1ll * rec[i] * f[i][tmp[j]] % MOD;
      }
      memset(g,0,sizeof g);
      for (int i = 1 ; i <= n ; ++ i) {
        g[i] = rec[i];
        for (int k = 1 ; k < i ; ++ k)
          (g[i] += -1ll * rec[i-k] * g[k] % MOD) %= MOD;
      }
      if (tmp.size()&1) (ans += g[n]) %= MOD;
      else (ans -= g[n]) %= MOD;
    }
    ans = (ans % MOD + MOD) % MOD;
    printf("%d
",ans); 
  }
  return 0;
}


小结:本题的关键在于想到容斥,以及枚举一维后dp另一维。这两个思路都有较广的适用性,有必要熟练运用。


以上是关于做题51NOD1518 稳定多米诺覆盖——容斥&dp的主要内容,如果未能解决你的问题,请参考以下文章

51Nod2903 稳定婚姻(Tarjan)

51Nod1317 相似字符串对 容斥原理 动态规划

51Nod1253 Kundu and Tree 容斥原理

51 nod 1439 互质对(Moblus容斥)

51nod 1317 相似字符串对(容斥原理+思维)

51nod1446 Kirchhoff矩阵+Gauss消元+容斥+折半DFS