[SMOJ1710]砖块II

97 篇文章 0 订阅
7 篇文章 0 订阅

题目描述

K 种不同规则的长方体砖块,长宽高分别是:1×1×1 2×1×1 3×1×1 …, K×1×1 。还给出一个 W×1×1 的地基,如下图所示, W =9, k =3,下面的是地基:

现在你要在地基上堆放砖块,必须满足如下的规则:

  1. 砖块只能横放,不能竖放。
  2. 砖块必须放置在整数位置,且不能越出地基。
  3. 砖块任何部分的正下方都必须要有其他砖块或者是地基。

例如:下图是不合法的放置方式,有 4 个不合法的地方:

我们定义一种堆放砖块方案的高度 height :它是指该方案中最高的砖块是第几层,其中地基是第 0 层,例如(图二)的高度是 3。
我们定义不同的堆放方案:例如有两种堆放方案 A 和 B,只要满足两个条件之一,方案 A 和方案 B 就是不同的方案:
1、 在某个位置方案 A 有砖块而方案B在该位置没有砖块,或者方案 B 有砖块而方案 A 没有。
2、 在某个位置方案 A 和方案 B 都有砖块,但是它们不是同一种规格的砖块。

给定地基的长度 W ,和地砖的最大长度 K,你的任务是计算有多少种不同的堆放砖块的方案,你的堆放砖块方案的高度 height 不能超过给定的 H 。答案模 1000000007。
例如:下图是 W=3 , k=3 , H=2 的所有合法方案:

输入格式 1710.in

多组测试数据。
第一行,一个整数 G ,表示有 G 组测试数据。 1G3
每组测试数据格式如下:
一行,三个整数 W H K 1W,H50 1KW

输出格式 1710.out

G 行,每行一个整数。

输入样例 1710.in

3
3 1 3
3 2 3
10 10 3

输出样例 1710.out

13
83
288535435


看到不同的方案,我想到了状态压缩,也许可以写个搜索试试看。虽然加了些剪枝,但是还是 TLE 了。于是又想到用 dp 做,但是状态却不知道如何表示才恰当。

最终还是在 lgj 的评讲之后才明白了。这题原来是个 dp 套 dp ,还是子问题的思想。

定义 f[i][j] 为宽度为 i 且高度为 j 时的方案总数,所求即为 f[W][H] 。表示出来不难,但是状态转移往往是最致命的地方。让我们考虑一下 f[i][j] 可能是由哪些状态转移来的。

以上图为例,当前宽度为 i ,高度为 j 。我们可以在宽度为 i 的地基上面的最右边搭一块长度为 L子地基(暂且这么称呼),于是左边部分是一个子问题,上面是一个子问题。我们再定义 c[L] 为组成一块长度为 L 的子地基的方案数。根据乘法原理,这种情况的方案数为 f[iL1][j]×f[L][j1]×c[l] ,而且因为 L<i (为什么不是小于等于?稍后会讨论),所以它们已经求解过,这种状态转移是可行的。

有一个问题,为什么左边部分的宽度取的不是 iL 而是 iL1 呢?原因:强行用一个空的位置隔开,不会取重复。什么意思呢?其实,紧挨着而中间没有空隙的方案,会包含在接下来讨论的子问题当中。

再来说,上面的为什么 L 不取到 i 呢?我们知道由于宽度取的是 iL1 ,如果 L 取到 i 的话,宽度就变成负数了,所以不能取到 i

也就是说,有一种情况是需要单独讨论的。当 L=i 时,这种情况的方案数实际上是 f[i][j1]×c[i] 。请仔细体会一下到目前为止的内容,再往下看。

现在我们只剩最后一个问题了,如何求出 c[] 数组?其实这个是比较简单的。

  • L=1 1时,显然只有 1 种方案;
  • L=2 时,可以用一块长度为 2 的大砖块,或者从 L=1 的基础上再加一块长度为 1 的砖块;
  • L=3 时,可以用一块长度为 3 的砖块,或者从 L=2 的基础上再加一块长度为 1 的砖块,或者从 L=1 的基础上再加一块长度为 2 的砖块;
  • …………
  • 注意当 L=k+2 时,有砖块长度限制,前一块可取的范围就要从2开始了。

由上述,可得

c[l]=i=1min(l,k)c[li]

参考代码:

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

using namespace std;

const int maxn = 55;
const int mod = 1e9 + 7;

long long c[maxn];
long long dp[maxn][maxn];

int main(void) {
    freopen("1710.in", "r", stdin);
    freopen("1710.out", "w", stdout);
    int g;
    scanf("%d", &g);
    while (g--) {
        int w, h, k;
        scanf("%d%d%d", &w, &h, &k);
        memset(c, 0, sizeof c);
        c[0] = 1;
        for (int i = 1; i <= w; i++)
            for (int j = 1; j <= min(i, k); j++) (c[i] += c[i - j]) %= mod;
        memset(dp, 0, sizeof dp);
        for (int i = 0; i <= w; i++) dp[i][0] = 1;
        for (int i = 0; i <= h; i++) dp[0][i] = 1;
        for (int i = 1; i <= w; i++)
            for (int j = 1; j <= h; j++) {
                for (int l = 0; l < i; l++)
                    (dp[i][j] += (dp[i - l - 1][j] * dp[l][j - 1]) % mod * c[l] % mod) %= mod;
                (dp[i][j] += dp[i][j - 1] * c[i]) %= mod;
            }
        printf("%I64d\n", dp[w][h]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值