洛谷P2051状压DP

https://www.luogu.org/problemnew/show/P2051

下面摘自洛谷oi爷的分析:

因为每一行每一列的炮的数量\leq 2≤2

所以我们考虑记数组去存储有几列放了一个炮,有几列放了两个炮.

我们又需要考虑转移?

因此设出状态

  f[i][j][k]f[i][j][k]代表放了前ii行,有jj列是有一个棋子,有kk列是有2个棋子的合法方案数.

这个时候我们知道全部的列数,又知道一些情况的列数.

所以我们可以求出不放棋子的列数

单步容斥:空的=全部的-−合法的

空的序列=m-j-k=m−j−k

确定情况

  1. 我们可以在当前第ii行不放棋子.
  2. 我们可以在当前第ii行放一个棋子
  3. 我们可以在当前第ii行放两个棋子.

接下来就需要分类讨论这些情况.

分类讨论

一.不放棋子

我们可以直接继承上面的状态.即f[i][j][k]=f[i-1][j][k]f[i][j][k]=f[i−1][j][k]

二.放一个棋子

显然我们不会选择放在有两个棋子的列.

因此存在情况如下

洛谷P2051状压DP_第1张图片

解释:

放在一个棋子的列

我们在某一个有一个棋子列放置棋子,会使这一列变为有两个棋子.

即我们要得到f[i][j][k]f[i][j][k]需要在j+1j+1个有一个棋子的列放置棋子,变为jj个有一个棋子的列

而我们又会得到一个新的有两个棋子的列.因此我们之前必须有k-1k−1个有两个棋子的列.

即f[i-1][j+1][k-1]f[i−1][j+1][k−1]的状态可以传递给f[i][j][k]f[i][j][k]

而我们又可以在(j+1)(j+1)中的任何一列放置这一个棋子.

因此我们要\times (j+1)×(j+1)

放在没有棋子的列

在一个没有棋子的列放置棋子,我们会得到一个新的有一个棋子的列.

即我们要从j-1j−1得到jj.

而这个时候,我们有两个棋子的列的数量不会变,所以从kk传递即可.

即f[i-1][j-1][k]f[i−1][j−1][k]的状态可以传递给f[i][j][k]f[i][j][k]

又因为我在空列中的任何一列放置这个棋子.

所以要\times× (m-(j-1)-k)(m−(j−1)−k)

三.放两个棋子

这个时候情况会多一个.先请大家自己考虑一下.

这个时候存在情况如下

洛谷P2051状压DP_第2张图片

解释

一个放在有一个棋子的列,一个放在没有棋子的列

这个时候,我们放置之后 :

一个没有棋子的列会变成一个有一个棋子的列,而一个有一个棋子的列会变成一个有两个棋子的列。

此时我们发现,

​ 有一个棋子的列的数量不会变,因此第二维依旧为jj,

​ 又因为我们会新增一个有两个棋子的列,所以我们需要从k-1k−1转移过来.

又因为我们可以在有一个棋子的列随便放,空列随便放.

根据乘法原理,需要\times j \times (m-j-(k-1))×j×(m−j−(k−1))

都放在没有棋子的列

此时我们放置之后

​ 会增加两个新的有一个棋子的列.

因此我们需要从j-2j−2转移过来.

而两个棋子的列的数量并不会改变,所以依旧为kk

又因为在空列中我们随便放.

根据组合数学,需要 C_{m-(j-2)-k}^{2}×Cm−(j−2)−k2​

都放在有一个棋子的列

我们放置在有一个棋子的列之后:

​ 这两个有一个棋子的列都会变成有两个子的列.

​ 即j+2j+2变成jj,从k-2k−2变成kk

又因为这些有一个棋子的列我们随便选择.

根据组合数学,需要 C_{j+2}^{2}×Cj+22​

#include
using namespace std;
typedef long long ll;
const int N = 1e5 + 100;
const int M = 1e7 + 100;
const int INF = 0x3f3f3f3f;
const int mod = 9999973;
ll dp[110][110][110],n,m;
int main()
{
    cin >> n >> m;
    dp[0][0][0] = 1;
    for(int i = 1;i <= n;i ++){
        for(int j = 0;j <= m;j ++){
            for(int k = 0;k + j <= m;k ++){
                //不放棋子
                dp[i][j][k] = (dp[i][j][k] + dp[i-1][j][k]) % mod;
                //放一个棋子
                if(j >= 1) dp[i][j][k] = (dp[i][j][k] + dp[i-1][j-1][k] * (m-k-(j-1))) % mod;
                if(k >= 1) dp[i][j][k] = (dp[i][j][k] + dp[i-1][j+1][k-1] * (j+1)) % mod;
                //放两个棋子
                if(k >= 1) dp[i][j][k] = (dp[i][j][k] + dp[i-1][j][k-1] * j * (m-j-(k-1))) % mod;
                if(j >= 2) dp[i][j][k] = (dp[i][j][k] + dp[i-1][j-2][k] * ((m-(j-2)-k)*(m-(j-2)-k-1)/2%mod)) % mod;
                if(k >= 2) dp[i][j][k] = (dp[i][j][k] + dp[i-1][j+2][k-2] * (((j+2)*(j+1))/2%mod)) % mod;
                if() dp[i][j][k] = (dp[i][j][k] + dp[i-1][])

                dp[i][j][k] %= mod;
            }
        }
    }
    ll ans = 0;
    for(int i = 0;i <= m;i ++){
        for(int j = 0;j + i <= m;j ++){
            ans += dp[n][i][j];
            ans %= mod;
        }
    }
    cout << ans%mod;
    return 0;
}

 

你可能感兴趣的:(动态规划-状压DP)