保研机试训练Day-28

数位DP && 状态压缩DP

Posted by Jeffzzc on April 20, 2025

数位DP

计数问题

给定两个整数 ab,求 ab 之间的所有数字中 09 的出现次数。

例如,a=1024,b=1032,则 a 和 b 之间共有 9 个数如下:

1024 1025 1026 1027 1028 1029 1030 1031 1032

其中 0 出现 10 次,1 出现 10 次,2 出现 7 次,3 出现 3 次等等…

#include<bits/stdc++.h>
using namespace std;
const int INF = 2e9;
const int N = 1010;
int n,m;
char a[N],b[N];
int f[N][N];

int main(){
    scanf("%d%s",&n, a + 1);
    scanf("%d%s",&m, b + 1);
  
    for(int i=0;i<=m;i++) f[0][i] = i;
    for(int i=0;i<=n;i++) f[i][0] = i;
  
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            f[i][j] = min(f[i-1][j]+1,f[i][j-1]+1);
            if(a[i]==b[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);
            else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
        }
  
    printf("%d\n",f[n][m]);
    return 0;
}

状态压缩类DP

整数划分

求把 N×M 的棋盘分割成若干个 1×2 的长方形,有多少种方案。

例如当 N=2M=4 时,共有 5 种方案。当 N=2,M=3 时,共有 3 种方案。

思路:所谓的状态压缩DP,就是用二进制数保存状态。为什么不直接用数组记录呢?因为用一个二进制数记录方便作位运算。前面做过的八皇后,八数码,也用到了状态压缩。本题等价于找到所有横放 1 X 2 小方格的方案数,因为所有横放确定了,那么竖放方案是唯一的。用f[i][j]记录第i列第j个状态。j状态位等于1表示上一列有横放格子,本列有格子捅出来。转移方程很简单,本列的每一个状态都由上列所有“合法”状态转移过来f[i][j] += f[i - 1][k]

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 12, M = 1 << N;

int n,m;
long long f[N][M];// 第一维表示列, 第二维表示所有可能的状态
bool st[M];//存储每种状态是否有奇数个连续的0,如果奇数个0是无效状态,如果是偶数个零置为true。

int main(){
    while(cin >> n >> m, n | m){  //读入n和m,并且不是两个0即合法输入就继续读入
        memset(f, 0, sizeof f);
  
        //第一部分:预处理1
        //对于每种状态,先预处理每列不能有奇数个连续的0
  
        for(int i = 0; i < (1 << n); i++){
            st[i] = true; // 某种状态没有奇数个连续的0则标记为true
            int cnt = 0;  // 记录连续的0的个数
            for(int j = 0; j < n; j++){  //遍历这一列,从上到下
                if(i >> j & 1){
                    // i >> j位运算,表示i(i在此处是一种状态)的二进制数的第j位
                    // &1为判断该位是否为1,如果为1进入if
                    if(cnt & 1) st[i] = false; //这一位为1,看前面连续的0的个数,如果是奇数(cnt&1为真)则该状态不合法
                    cnt = 0;
                }
                else cnt++;
            }
            if(cnt & 1) st[i] = false;
        }
  
        f[0][0] = 1;
        for(int i = 1; i <= m; i++)
            for(int j = 0; j < 1 << n; j++)
                for(int k = 0; k < 1 << n; k++)
                    if((j & k) == 0 && st[j | k])
                        f[i][j] += f[i - 1][k];
  
        cout << f[m][0] << endl;
    }
    return 0;
}