浅谈轮廓线DP

浅谈轮廓线DP

本文将粗略讲解轮廓线 DP 的基础原理、状态设计与实现模板,结合经典网格计数例题帮助读者掌握该算法。若文章存在错误或优化建议,欢迎在评论区留言指正。

前置说明

阅读本文需要掌握状压 DP 基础知识点。若无相关基础,可自行查阅前置资料,后续会更新状压 DP 基础讲解文章。

配套例题

建议结合两道经典网格 DP 例题实操学习,可快速吃透轮廓线 DP 核心逻辑:

P1879 USACO06NOV Corn Fields G:网格种草计数基础模板题

P1896 SCOI2005 互不侵犯:八方向限制拓展练习题

一、算法优势

轮廓线 DP 是面向网格、棋盘模型的状压 DP 进阶算法,能够解决普通逐行状压 DP 无法覆盖的网格计数问题,算法效率远高于朴素 DFS。

该算法采用逐格转移的方式,计算精度更高;仅存储局部轮廓线状态,时空复杂度更优,是 12 * 12 及以内小规模网格计数问题的最优解法。

二、核心解题思路

当题目网格中存在上下、左右相邻格子的放置限制、禁止相邻选取等约束条件时,可优先使用 轮廓线 DP 。本文将结合经典例题 P1879 USACO06NOV Corn Fields G 完整讲解算法原理与实现。

2.1 状态设计

定义记忆化 DP 数组 dpijs,其中 i,j 为当前遍历的网格坐标(下标从 0 开始),s 为轮廓线状态压缩值。

状态释义:s 的第 0 ~ j-1 位,记录当前第 i 行已遍历列的格子状态;s 的第 j 位及后续位数,记录上一行(i-1 行)对应列的格子状态。数组存储对应位置、对应轮廓线状态下的合法方案总数。

为方便理解状态转移逻辑,结合网格状态示例进行说明:

0 列 1 列 2 列 3 列 4 列 5 列 6 列
0 行 1 0 1 0 0 0 0
1 行 1 0 0 1 0 1 1

遍历至不同坐标时,轮廓线状态 s 的取值如下:

在 i=1,j=0 时,s=1010000

在 i=1,j=1 时,s=1010000

在 i=1,j=2 时,s=1010000

在 i=1,j=3 时,s=1000000。

在 i=1,j=4 时,s=1001000。

在 i=1,j=5 时,s=1001000。

在 i=1,j=6 时,s=1001010。

在 i=1,j=7 时,s=1001011。

2.2 完整代码实现

为适配书写习惯,代码中将题目输入的行列数进行互换,最终代码可正常 AC 对应题目,全文下标从 0 开始。

cpp 复制代码
// 全文使用 based-0
#include<iostream>
using namespace std;
#define mod 100000000
int dp[15][15][5000];
int k[15][15];
int n,m;

int set(int s,int i,int v) //设置状态
{
    return v==0?s&(~(1<<i)):s|(1<<i);
}

int get(int s,int i) //获取状态
{
    return (s>>i)&1;
}

int f(int i,int j,int s) //轮廓线DP
{
    if(i==n)return 1; //全部dp完了,证明此方案有效,返回1
    if(j==m)return f(i+1,0,s); //下一行
    if(dp[i][j][s]!=-1)return dp[i][j][s]; //记忆化
    int ans;
    ans=f(i,j+1,set(s,j,0))%mod; //不种草一定可以,所以直接下一列
    if(((j>0&&(!get(s,j-1))&&(!get(s,j)))||(j==0&&(!get(s,j))))&&k[i][j]) //当且仅当上一行和左边一列状态为0(没种菜),且这个地方可以种草
        ans=(ans+f(i,j+1,set(s,j,1)))%mod;
    return dp[i][j][s]=ans; //记忆化
}

int main()
{
    cin>>n>>m; //n和m互换
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            cin>>k[i][j];
    for(int i=0;i<=n;i++)
        for(int j=0;j<=m;j++)
            for(int p=0;p<=(1<<12);p++)
                dp[i][j][p]=-1; //标记为未访问
    cout<<f(0,0,0);
    return 0;
}

看到这里可能你还不太理解,所以附上一个友情链接

三、学习总结

熟练掌握本文的状态设计思路与模板代码,并独立通过两道配套例题,即可掌握轮廓线 DP 的基础用法,能够独立解决基础网格限制类计数问题。

若对文章内容有补充建议、发现笔误或存在算法疑问,可在评论区留言或私信交流探讨。