Unverified Commit 03d4b4b1 authored by Shuhao Zhang's avatar Shuhao Zhang Committed by GitHub
Browse files

rewrite state dp

parent 7f8f7903
Loading
Loading
Loading
Loading
+61 −36
Original line number Diff line number Diff line
学习状压 dp 之前,请确认你已经完成了[动态规划初步](/dp/)部分内容的学习
学习状压 dp 之前,请确认你已经完成了 [动态规划初步](/dp) 部分内容的学习

(建议学习[位运算](/math/bit/)部分的内容)
同时建议学习 [位运算](/math/bit) 部分的内容)

## 状压 DP 简介

状压 dp 是动态规划的一种,借由将状态压缩(通常压缩为某整形)以达到节约空间和时间的目的
状压 dp 是动态规划的一种,通过将状态压缩为整数来达到优化转移的目的

### 常用格式
## 例题

```cpp
int maxn = 1 << n;  //规定状态的上界
for (int i = 0; i < maxn; i++) {
  if (i & (i << 1)) continue;  //如果i情况不成立就忽略
  Type[++top] = i;             //记录情况i到Type数组中
}
for (int i = 1; i <= top; i++) {
  if (fit(situation[1], Type[i])) dp[1][Type[i]] = 1;  //初始化第一层
}
for (int i = 2; i <= 层数(dp上界); i++) {
  for (int l = 1; l <= top; l++)  //穷举本层情况
    for (int j = 1; j <= top; j++)  //穷举上一层情况(上一层对本层有影响时)
      if (situation[i], Type[l] Type[j] 符合题意)
        dp[i][l] = dp[i][l] + dp[i - 1][j];  //改变当前层(i)的状态(l)的方案种数
}
for (int i = 1; i <= top; i++) ans += dp[上界][Type[i]];
```
??? note "[「SCOI2005」互不侵犯](https://loj.ac/problem/2153)"
    在 $ N \times N $ 的棋盘里面放 $ K $ 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 $ 8 $ 个格子。

### 典型例题
我们用 $ f(i,j,l) $ 表示前 $ i $ 行,当前状态为 $ j $ ,且已经放置 $ l $ 个国王时的方案数。

[\[USACO06NOV\]玉米田 Corn Fields](https://www.luogu.org/problemnew/show/P1879)
其中 $ j $ 这一维状态我们用一个二进制整数表示( $ j $ 的某个二进制位为 0 代表对应的列不放国王,否则代表对应的列放国王)。

显然,这是一道典型的动态规划题目,但由于方案数过多,应使用状压 dp 避免超时
我们需要在刚开始的时候预处理出一行的所有合法状态 $ sta(x) $ (排除同一行内两个国王相邻的不合法情况),在转移的时候枚举这些可能状态进行转移。

本题所 "压缩" 的是 "每行可行的状态" 和 "每行土地的状态", 而储存答案的 dp 数组就应同时体现这两个特点(所以本题 dp 数组为二维)
设当前行的状态为 $ j $ ,上一行的状态为 $ x $ ,可以得到下面的转移方程: $ f(i,j,l) = \sum f(i-1,x,l-sta(x)) $ 。

具体实现方法同上方伪代码
需要注意在转移时排除相邻两行国王互相攻击的不合法情况。

[例题代码](https://www.luogu.org/paste/kto3ua68)
??? "参考代码"
    ```cpp
    #include <iostream>
    #include <algorithm>
    using namespace std;
    long long sta[2005],sit[2005],f[15][2005][105];
    int n,k,cnt;
    void dfs(int x,int num,int cur)
    {
     if(cur>=n)//有新的合法状态
     {
      sit[++cnt]=x;
      sta[cnt]=num;
      return;
     }
     dfs(x,num,cur+1);//cur位置不放国王
     dfs(x+(1<<cur),num+1,cur+2);//cur位置放国王,与它相邻的位置不能再放国王
    }
    int main()
    {
     cin>>n>>k;
     dfs(0,0,0);//先预处理一行的所有合法状态
     for(int i=1;i<=cnt;i++)
      f[1][i][sta[i]]=1;
     for(int i=2;i<=n;i++)
      for(int j=1;j<=cnt;j++)
       for(int l=1;l<=cnt;l++)
       {
        if(sit[j]&sit[l])continue;
        if((sit[j]<<1)&sit[l])continue;
        if(sit[j]&(sit[l]<<1))continue;
        //排除不合法转移
        for(int p=sta[j];p<=k;p++)
         f[i][j][p]+=f[i-1][l][p-sta[j]];
       }
     long long ans=0;
     for(int i=1;i<=cnt;i++)
      ans+=f[n][i][k];//累加答案
     cout<<ans<<endl;
     return 0;
    }

### 几道练习题

[NOI2001 炮兵阵地](https://www.luogu.org/problemnew/show/P2704)

[SCOI2005 互不侵犯](https://www.lydsy.com/JudgeOnline/problem.php?id=1087)
[\[USACO06NOV\]玉米田 Corn Fields](https://www.luogu.org/problemnew/show/P1879)

[AHOI2009 中国象棋](https://www.lydsy.com/JudgeOnline/problem.php?id=1801)