轮廓线动态规划入门讲解-UVA11270

博客围绕用1*2多米诺骨牌填充n*m矩形的问题展开,介绍了轮廓线动态规划方法。它是状态压缩动态规划的一种,将问题看作多段图问题。对于每个格子,考虑往左和往上放牌情况,通过轮廓线压缩状态,还给出了状态转移的具体规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

给出一个 n ∗ m n*m nm的矩形,然后用 1 ∗ 2 1*2 12大小的多米若骨牌去填充 n ∗ m n*m nm的这个矩形,问有多少种填充方法。 n ∗ m &lt; = 100 n*m&lt;=100 nm<=100

题外话:从刘汝佳的算法竞赛入门经典学习来的,这里自己总结一下

轮廓线动态规划

轮廓线动态规划是状态压缩动态规划的一种,只不过不是一行一行的压,而是一条一条地像轮廓线一样的压。
这种题目可看做多段图的问题,可以根据一下的dp代码进行转换。

//设d[i][j]表示从阶段1到节点(i,j)的路径条数
now=0;
所有d[0][j]初始化为1
从小到大枚举每个要算的阶段{
	now^=1;
	所有d[now][j]初始化为0 //滚动数组
	for 上一阶段的每个节点j
		for j的每一个后继节点k
			if 满足某一条件
				d[now][k]+=d[1-now][j]
}
最后d[now]为最后阶段的数组

分析

这是一道轮廓线动态规划的经典题目。对于每一个 ( i , j ) (i,j) (i,j),我们考虑往左和往上放。若 ( i , j ) (i,j) (i,j)不在边界,那么能
否放仅由 ( i − 1 , j ) 和 ( i , j − 1 ) (i-1,j)和(i,j-1) (i1,j)(i,j1)是否放了多米诺骨牌来决定的。相当于对于 ( i , j ) (i,j) (i,j)能否放牌这个阶段,仅能有两个阶段
( i − 1 , j ) (i-1,j) (i1,j) ( i , j − 1 ) (i,j-1) (i,j1)状态决定,然而由于这两个状态不在同一行,不能同时压缩两行。所以考虑轮廓线压缩。同时这道
题符合多段图模型,所以设 d [ i ] [ j ] d[i][j] d[i][j]表示以当前格子为右下角,要不要放骨牌以及放那种骨牌。用一个数的二进制来表示轮廓线,轮廓线如下所示。
状态如下所示,图为棋盘

 【  】  【  】【  】【  】【  】
 【  】  【  】【  】【  】【  】
 【  】  【  】【k4】【k3】【k2】
 【k1】  【k0】 (i,j)

用m位二进制整数g表示 k 4 k 3 k 2 k 1 k 0 k_4k_3k_2k_1k_0 k4k3k2k1k0是否被覆盖。
考虑状态转移:

  1. 不放 。若 k 4 = 0 k_4=0 k4=0,则不能转移,因为棋子不可能放满。所以只有当 k 4 = 1 k_4=1 k4=1时才能转移到 k 3 k 2 k 1 11 k_3k_2k_111 k3k2k111的状态
 【  】  【  】【  】【  】【  】
 【  】  【  】【  】【  】【  】
 【  】  【  】【1 】【k3】【k2】
 【k1】  【k0】【0
  1. 往上放。只有当 k 4 k_4 k4=0 且i不是最上面一行时,才能放下,转移到 k 3 k 2 k 1 k 0 1 k_3k_2k_1k_01 k3k2k1k01的状态
【  】  【  】【  】【  】【  】
【  】  【  】【  】【  】【  】
【  】  【  】【1 】【k3】【k2】
【k1】  【k0】【1
  1. 往左放。当 k 0 = 0 k_0=0 k0=0且j不是最左列时,且 k 4 = 1 k_4=1 k4=1才能放下,转移到 k 3 k 2 k 1 11 k_3k_2k_111 k3k2k111状态。
【  】  【  】【  】【  】【  】
【  】  【  】【  】【  】【  】
【  】  【  】【1 】【k3】【k2】
【k1】  【1 】【1

完整代码

/*******************************
Author:galaxy yr
LANG:C++
Created Time:2019/6/24 20:50:23
*******************************/
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=15;
int n,m,now;
long long d[2][1<<maxn],ans[120][120];
int main()
{
    memset(ans,-1,sizeof ans);
    while(scanf("%d%d",&n,&m)==2)
    {
        if(n*m%2==1){printf("0\n");continue;}
        if(m==0 and n==0)return 0;
        if(n<m)swap(n,m);
        if(~ans[n][m]){printf("%lld\n",ans[n][m]);continue;}
        memset(d,0,sizeof d);
        d[0][(1<<m)-1]=1;
        now=0;
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
            {
                now^=1;
                memset(d[now],0,sizeof d[now]);
                for(int k=0;k<(1<<m);k++)
                {
                    if(k&(1<<m-1)) d[now][(k<<1)^(1<<m)]+=d[1-now][k];
                    if(i and !(k&(1<<m-1)))d[now][(k<<1)^1]+=d[1-now][k];
                    if(j and (k&(1<<m-1)) and !(k&1))d[now][(k<<1)^(1<<m)|(1<<1)^1]+=d[1-now][k];
                }
            }
        printf("%lld\n",d[now][(1<<m)-1]);
        ans[n][m]=d[now][(1<<m)-1];
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值