算法:轮廓线dp

什么是轮廓线DP

  • 适用范围: 较窄的棋盘( m × n m \times n m×n m m m或者 n n n较小)。按整行或者整列无法进行转态转移。而把轮廓线作为状态一部分。具体见例题。

例题一:铺砖问题(Poj 2411)

我们在状压DP中已经介绍过一种整行状态转移的方法,但是这里我们采用轮廓线的方法解决。对每个小格,我们定义0为非覆盖,1为覆盖。
对每一个小格,其轮廓线包含了该小格和(1) 该小格前面,且 (2)确定该小格后状态还不确定的小格。如图红色框所示。(因为前面的其他小格必定已经全部铺满了,因此不必再考虑)
在这里插入图片描述
因此对每个小格对应的轮廓线里的小格,考虑不确定性,共有 2 m 2^m 2m 个状态。因此对每个小格,我们需要分配 2 m 2^m 2m个状态。因此:

  • 定义dp [ c u r ] [ S ] [cur][S] [cur][S]: 当前所讨论小格轮廓线内状态为 S S S时(例子中 S = k 1 k 0 O ( 2 进 制 ) S = {k_1k_0O}_{(2进制)} S=k1k0O(2),当前小格和当前小格前面所有小格组成区域(绿色圈内区域)的总共铺放方法数。
  • 目标状态: d p [ l a s t ] [ 2 m − 1 ] dp[last][2^m-1] dp[last][2m1]
  • 状态转移:
    • 选择:对每个小格,由于我们只讨论其对前面区域的影响,故可以选择(1): 不放。(2):左放。(3):上放。
      • 不放: (1): O = 0 O=0 O=0,,(2)前一状态的 k 2 k_2 k2为1
      • 左放: (1): 当前小格不在第一列。(2): k 0 = O = 1 k_0 = O = 1 k0=O=1。(3):前一状态的 k 0 = 0 , k 2 = 1 k_0 = 0,k_2=1 k0=0k2=1
      • 上放: (1): 当前小格不在第一排。(2): O = 1 O=1 O=1。 (3): 前一状态的 k 2 = 0 k_2 = 0 k2=0
        在这里插入图片描述
        这里不铺和其余两种状态是互斥的。
    • 滚动数组:当前小格是在前一个小格的基础上讨论,故采用滚动数组。用:
      • d p [ x ] [ S ] dp[x][S] dp[x][S]: 表示前一个小格的状态。
      • d p [ 1 − x ] [ S ] dp[1-x][S] dp[1x][S]: 表示后一个小格的状态。 x ∈ { 0 , 1 } . x \in \{0, 1\}. x{0,1}.
    • 根据转移的选择以及条件,有状态转移方程式:
      A = d p [ 1 − c u r ] [ ( s &gt; &gt; 1 ) ∣ ( 1 &lt; &lt; ( m − 1 ) ) ] B = d p [ 1 − c u r ] [ ( ( s &gt; &gt; 1 ) ∣ ( 1 &lt; &lt; ( m − 1 ) ) ) &amp; ( ( 1 &lt; &lt; m ) − 2 ) ] C = d p [ 1 − c u r ] [ s &gt; &gt; 1 ] \begin{aligned} A &amp;= dp[1-cur][(s&gt;&gt;1) | (1 &lt;&lt; (m-1))] \\ B &amp;= dp[1-cur][((s &gt;&gt; 1) | (1 &lt;&lt; (m-1))) \&amp; ((1&lt;&lt;m)-2)] \\ C &amp;= dp[1-cur][s &gt;&gt; 1] \\ \end{aligned} ABC=dp[1cur][(s>>1)(1<<(m1))]=dp[1cur][((s>>1)(1<<(m1)))&((1<<m)2)]=dp[1cur][s>>1]
      d p [ c u r ] [ S ] = { A 当 前 S 满 足 不 铺 条 件 B 当 前 S 满 足 左 铺 条 件 C 当 前 S 满 足 上 铺 条 件 B + C 当 前 S 满 足 左 铺 和 上 铺 条 件 dp[cur][S] = \begin{cases} A &amp; 当前S满足不铺条件 \\ B &amp;当前S满足左铺条件 \\ C &amp;当前S满足上铺条件 \\ B+C &amp;当前S满足左铺和上铺条件 \\ \end{cases} dp[cur][S]=ABCB+CSSSS
      这里A对应不放,B对应左放,C对应上放的前一小格对应状态数量。
  • 更新策略: 二维数组循环遍历更新
    在这里插入图片描述
  • 初始状态: d p [ 0 ] [ 2 m − 1 ] dp[0][2^m-1] dp[0][2m1] = 1,其余为0。(这里人为想象一个第0排,由于第一排不能上放,因此第0排需要全为1, 初始状态对应的是假想第0排的最后一个小格)

在这里插入图片描述

  • AC代码:
#include<iostream>
#include<cstring>
using namespace std;

int m, n;
const int MAX = 1 << 13;
typedef long long ll;
ll dp[2][MAX];
int main()
{
    int cur;
    while(cin >> m >> n)
    {
        if(m + n == 0)
            return 0;
        if(n < m)
            swap(n, m);
        memset(dp,0,sizeof(dp));
        cur = 0;
        dp[cur][(1<<m)-1] = 1;
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<m; j++)
            {
                cur ^= 1;
                memset(dp[cur], 0, sizeof(dp[cur]));
                for(int s=0; s<(1<<m); s++)
                {
                    // 不放
                    if(!(s&1))
                        dp[cur][s] += dp[1-cur][(s>>1) | (1 << (m-1))];
                    else
                    {
                        // 左放
                        if(j && (s >> 1 & 1))
                            dp[cur][s] += dp[1-cur][((s >> 1) | (1 << (m-1))) & ((1<<m)-2)];
                        // 上放
                        if(i)
                            dp[cur][s] += dp[1-cur][s >> 1];
                    }

                }
            }
        }
        cout << dp[cur][(1<<m)-1] << endl;
    }
}



例题二:铺砖问题(挑战程序设计竞赛P196)

  • 题意:
    在这里插入图片描述
  • 思路:
    可以看到,这道题中加入了黑色砖不用铺,也不能够铺的限制。我们遵循例题一同样的思路,但是我们认为黑色砖衡为被覆盖,其值永远1。当我们对一个状态 d p [ c u r ] [ S ] dp[cur][S] dp[cur][S]进行讨论时。
  • 若该状态对应小格为黑砖。
    • S &amp; 1 = 0 S \&amp;1 = 0 S&1=0: 那么不可能存在这样的状态,自然 d p [ c u r ] [ S ] = 0 dp[cur][S] = 0 dp[cur][S]=0(当前讨论状态所在小格的覆盖情况是 S S S的最后一位,而我们把黑砖作为衡为1的覆盖)
    • S &amp; 1 = 1 S \&amp;1 = 1 S&1=1: 由于该小格不能铺砖,因此上方小格已经铺好。
      在这里插入图片描述
  • 若该状态对应的小格不是黑砖,可以选择。
    • 不放: (1): O = 0 O=0 O=0,,(2)前一状态的 k 2 k_2 k2为1
    • 左放: (1): 当前小格不在第一列。(2): k 0 = O = 1 k_0 = O = 1 k0=O=1。(3):前一状态的 k 0 = 0 , k 2 = 1 k_0 = 0,k_2=1 k0=0k2=1(4): 左边的小格不可以是黑砖。
    • 上放: (1): 当前小格不在第一排。(2): O = 1 O=1 O=1。 (3): 前一状态的 k 2 = 0 k_2 = 0 k2=0(4): 上面的小格不可以是黑砖。
#include<iostream>
#include<cstring>
using namespace std;

int m, n;
const int MAX = 1 << 13;
typedef long long ll;
ll dp[2][MAX];
char color[MAX][MAX];
int main()
{
    int cur;
    while(cin >> n >> m)
    {
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<m; j++)
            {
                cin >> color[i][j];
            }
        }
        memset(dp,0,sizeof(dp));
        cur = 0;
        dp[cur][(1<<m)-1] = 1;
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<m; j++)
            {
                cur ^= 1;
                memset(dp[cur], 0, sizeof(dp[cur]));
                for(int s=0; s<(1<<m); s++)
                {
                    // --------------------加入代码--------------------------------
                    if(color[i][j] == 'x')
                    {
                    	/*如果当前格为黑色,那么该轮廓线最后一个2进制位只能为1。
                    	按状态定义,若最后2进制位为0,则铺法为0.
                    	*/
                        if(!(s&1))
                            dp[cur][s] = 0;
                        /*如果当前格为黑色,且轮廓线最后一个2进制位1,则上方一个格子只能为1*/。
                        else
                            dp[cur][s] += dp[1-cur][(s>>1) | (1 << (m-1))];

                    }
                    // -----------------------------------------------------------------
                    else
                    {
                        // 不放
                        if(!(s&1))
                            dp[cur][s] += dp[1-cur][(s>>1) | (1 << (m-1))];
                        else
                        {
                        	// --------------------------增加限制条件----------------------------//
                            	// 左放
                            if(j && (s >> 1 & 1) && color[i][j-1]!='x')
                                dp[cur][s | 3] += dp[1-cur][((s >> 1) | (1 << (m-1))) & ((1<<m)-2)];
                            	// 上放
                            if(i && color[i-1][j] != 'x')
                                dp[cur][s] += dp[1-cur][s >> 1];
                           // -------------------------------------------------------------------------//
                        }
                    }

                }
            }
        }
        cout << dp[cur][(1<<m)-1] << endl;
    }
}


  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MATLAB中的轮廓线连通算法是一种用于提取图像中物体轮廓的算法。它可以将图像中相邻的像素点连接起来,形成一个闭合的轮廓线。 MATLAB提供了多种轮廓线连通算法,其中最常用的是基于连通组件标记的算法。该算法的基本思想是通过扫描图像,将相邻的像素点分为不同的连通组件,并为每个组件分配一个唯一的标记。然后,根据这些标记,可以提取出各个连通组件的轮廓线。 在MATLAB中,可以使用函数`bwlabel`来实现轮廓线连通算法。该函数接受一个二值图像作为输入,并返回一个标记矩阵,其中每个像素点都被标记为一个整数值,表示所属的连通组件。然后,可以使用函数`bwboundaries`来提取出各个连通组件的轮廓线。 下面是一个示例代码,演示了如何使用MATLAB进行轮廓线连通算法: ```matlab % 读取二值图像 binaryImage = imread('image.png'); % 进行连通组件标记 labeledImage = bwlabel(binaryImage); % 提取轮廓线 boundaries = bwboundaries(labeledImage); % 绘制轮廓线 figure; imshow(binaryImage); hold on; for k = 1:length(boundaries) boundary = boundaries{k}; plot(boundary(:,2), boundary(:,1), 'r', 'LineWidth', 2); end hold off; ``` 这段代码首先读取了一个二值图像,然后使用`bwlabel`函数进行连通组件标记,得到一个标记矩阵。接着,使用`bwboundaries`函数提取出各个连通组件的轮廓线,并将其绘制在原图像上。 希望以上内容对您有帮助!如果您还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值