首页 > 试题广场 >

小军砌墙

[编程题]小军砌墙
  • 热度指数:228 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 128M,其他语言256M
  • 算法知识视频讲解
【注意:本题按通过的Case比例给分】


小军接到一项工作,要用红色和绿色两种砖块建一面宽度W高度H的墙

小军只有以下两种砖块可以用:


红色砖块宽度2,高度1

绿色砖块宽度3,高度1

 

每种砖块只能按照以上的横向摆放,不能竖起来

横向两块方块之间是有缝的,因此为了让建好墙更稳固,小军会避免在任意相邻的两行间,除了左右两侧以外,出现任何上下对齐的砖缝,请参看下图:


上面一种建造方式是允许的,而下面一种建造方式不允许

特别的,下面这两种宽度为32的墙也是允许的


给定一面墙的宽度和高度后,请帮小军计算在以上规则允许的前提下,一共有多少种不同的建墙方式



输入描述:
两个整数 W 和 H (2 <= W <= 30, 1 <= H <= 10)


输出描述:
一个整数,所有可能建墙方式的数量。这个数量可能会大于32位整数的表示范围,请用64位整数
示例1

输入

5 2

输出

2
示例2

输入

9 5

输出

14

[编程题]小军砌墙 难得离谱 讲的明白 位运算+动态规划

这套题真的难的离谱,2小时做完的都是些什么神仙?

截止5月11日,还没有题解,就让我来抛砖引玉吧。

首先咱们最容易想到的是类似dfs那种递归算法,这个我也贴在后面了,但是超时了。

我重点来讲解下面的 位运算+动态规划 算法。

首先我们来考虑铺一行的可能性,我们定义一个函数f(pos int)来从pos位置开始铺砖,那么:
1.我们可以铺一块2格的红砖,然后调用f(pos+2)
2.我们可以铺一块3格的绿砖,然后调用f(pos+3)

然后在递归搜索的过程中用什么记录状态呢,可以用bool值数组来记录,为1表示一块转结束的位置。
例如 w=9 时可以得到下列状态码:
S1:010101001
S2:010100101
S3:010010101
S4:001010101
S5:001001001

要存储这样的状态码可能最容易想到的是bool数组,但是注意到题干里限制了 2 <= W <= 30,因此咱们可以大胆一点,可以使用int32来作为32位bool来用,省空间是小事,主要是后面我们需要对比两行的间隙,相比与遍历两个bool数组,直接对两个int32进行 位与 效率就高多了。

好了,咱们现在有了铺满9格的5种可行的状态码,那么只有一行的时候每种状态码都只可能出现1次,咱们可以建立一个数组dp[5]来记录每种状态码可能出现的次数,其中每个元素均为1。

那多行呢?题干要求相邻的两行不能出现任何上下对齐的砖缝。
那怎么判断缝隙对没对齐呢?很简单,位与一下。
比如我们当时按S1铺第1行,按S3铺第2行是否符合要求?
只需要将两个状态码 位与,得S:010000001,除了末尾,中间有一处对齐了,因此非法。
如果两个状态码 位与 得S:000000001,则中间没有对齐的缝隙,这个组合是合法的。

进一步,我们可以根据上一行的可能性数组和状态转移来计算。
例如,容易判断S2,S3->S5可行,那么这一行按照S5来铺的可能性等于上一行按S2,S3来铺的可能性之和,实际上就是运用动态规划的思想。

下面是Golang代码,没有什么高级语法,就算你学的Java/Python等应该也都能读懂。

如果这篇解答对你有帮助,不妨点个赞吧!

//位运算+动态规划
package main

import "fmt"

var w,h int

//可行的间隙码
var gaps []int

//铺一行
func f(pos int,gap int){
    if pos>w{            //越界
        return
    }else if pos==w{    //已铺完
        gaps=append(gaps, gap)
    }else{                //未铺完
        f(pos+2,gap<<2+1)
        f(pos+3,gap<<3+1)
    }
}

func main() {
    fmt.Scan(&w,&h)

    //不多于三行只有一种可能性
    if w<=3{
        fmt.Println(1)
        return
    }

    //开始搜索可行状态码
    f(0,0)

    //输出可行的间隙码
    //for _,v:=range gaps{
    //    fmt.Printf("S%d:%09b\n",i+1,v )
    //}

    //dp[i]表示当前行按照S[i]来铺的可能性
    dp:=make([]int, len(gaps))

    //初始化,单行的时候每一种状态均为1
    for i:=0;i< len(dp);i++{
        dp[i]++
    }

    //动态规划
    for i:=1;i<h;i++{
        tmp:=make([]int, len(gaps))
        for j:=0;j< len(dp);j++{
            for k:=0;k< len(dp);k++{
                if gaps[j]&gaps[k]==1 {
                    tmp[k]+=dp[j]
                }
            }
        }
        dp=tmp
    }

    //可能性求和
    ans:=0
    for _,v:=range dp{
        ans+=v
    }
    fmt.Println(ans)
}
//暴力搜索
package main

import "fmt"

var w,h int

type list []int

func (l list)has(i int) bool {
    for _,v:=range l{
        if v==i{
            return true
        }
    }
    return false
}

func f(line,pos int,preGaps,curGaps []int) int {
    if list(preGaps).has(pos) || pos>=w-1 || line>=h {
        return 0
    }else {
        ans:=0
        curGaps= append(curGaps, pos)
        if pos<w-3{
            ans+= f(line,pos+2,preGaps,curGaps)
            ans+= f(line,pos+3,preGaps,curGaps)
            return ans
        }else{
            if line==h-1{
                ans++
            }else{
                ans+= f(line+1,2,curGaps,make([]int,0))
                ans+= f(line+1,3,curGaps,make([]int,0))
            }
        }
        curGaps=curGaps[:len(curGaps)-1]
        return ans
    }
}

func main() {
    fmt.Scan(&w,&h)
    if w<=3{
        fmt.Println(1)
        return
    }
    ans:=0
    ans+=f(0,2,make([]int,0),make([]int,0))
    ans+=f(0,3,make([]int,0),make([]int,0))
    fmt.Println(ans)
}
发表于 2020-05-11 19:27:54 回复(1)
例如 w=9 时可以得到下列砌砖方法:
S1:010101001
S2:010100101
S3:010010101
S4:001010101
S5:001001001
#include<iostream>
#include<vector>
using namespace std;
int w,h;
vector<int >state;
vector<int >block = {2,3}; 
//穷举所有可能的组合
void dfs(int pos, int cur_state, vector<int>& state ) {
    if( (1<<w)& cur_state ) state.push_back(cur_state);
    else {
        for(auto& i : block){
            if( pos + i > w )continue;      //如果往后面添加一块砖超过宽度就舍弃
            dfs(pos + i, cur_state | (1<< (pos + i)), state);  //未越界就添加
        }
    }
}
int main() {
    cin >> w >> h;
    dfs(0, 0, state);
    int total_kinds = state.size();     //所有排列可能的种类
    vector<vector<long long>> dp(h, vector<long long >(total_kinds, 0));   //dp[i][j] --代表第i行第j中排列方式可能出现的情况
  
      //第一行无限制,所有每种排列可能出现的情况都是1
    for(int i = 0; i < total_kinds; ++i) {
        dp[0][i] = 1;
    }
    //从第二行开始,遍历每种情况和state库里的情况对比
    for(int i = 1; i < h; ++i) {
        for(int j = 0; j < total_kinds; ++j) {
            for(int k = 0; k < total_kinds; ++k){
                if( (state[j] & state[k]) != (1 << w) )continue;  //将两个状态码位与,除了末尾,还有一处对齐,非法舍弃。
                dp[i][j] += dp[i-1][k];    //合法情况,累加起来
            }
        }
    }
    long long ans = 0;
    for(int i = 0; i < total_kinds; ++i) {
        ans += dp[h-1][i];
    }
    cout<<ans;
}


编辑于 2022-03-17 11:58:12 回复(0)
  • dfs1用于求每一层总共的砌砖方法。
例如 w=9 时可以得到下列砌砖方法:
S1:010101001
S2:010100101
S3:010010101
S4:001010101
S5:001001001
  • dp[i][j]表示第i层中第j种砌砖的方法总共有多少种摆法。
  • dp[i][j]=sum(dp[i-1][k])  其中k是上一层中所有与j满足除了末尾都不重叠的砌法。例如S2,S3与S5。
下面贴出c++和python版本,python 日常超时
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int w, h;
vector<int> state;
long long dp[15][2000];
void dfs1(int i, int now, vector<int>& state){
	if(((1<<w)&now)!=0){state.push_back(now); return; }
	vector<int> a = { 2, 3 };
	for (auto &x :a){
		if (i + x>w) continue;
		int mask = 1 << (i + x);
		dfs1(i + x, now | mask, state);
	}
}
int main(){
	cin >> w >> h;
	for (int i = 0; i < 15; i++)
	for (int j = 0; j < 2000; j++)
		dp[i][j] = 1;
	dfs1(0, 0, state);
	int n = state.size();
	long long res = 0;
	for (int i = 1; i<h; i++)
	for (int j = 0; j < n; j++)
	{
		dp[i][j] = 0;
		for (int k = 0; k < n; k++){
			if ((state[k] & state[j]) != (1 << w)) continue;
			dp[i][j] += dp[i - 1][k];
		}
	}
	for (int i = 0; i < n; ++i) res += dp[h - 1][i];
	cout << res;
	return 0;
}
w,h=map(int,input().split())
state=[]
def dfs1(i,now):
    if 1<<w & now !=0:
        state.append(now)
        return
    for x in [2,3]:
        if i+x>w: continue
        mask=1<<(i+x)
        dfs1(i+x,now|mask)
dfs1(0,0)
dp=[[1]*len(state) for _ in range(h)]
for i in range(1,h):
    for j in range(len(state)):
        dp[i][j]=0
        for k in range(len(state)):
            if state[k]&state[j]!=1<<w:continue
            dp[i][j]+=dp[i-1][k]
res=0
for i in range(len(state)): res+=dp[h-1][i]
print(res)

编辑于 2020-08-11 01:08:11 回复(0)
位运算+状态规划
#include <bits/stdc++.h>
using namespace std;

int r = 2, g = 3;

void dfs(int cur, int w, int curstate, vector<int> &valid_states)
{
    if (cur == w)
    {
        curstate ^= 1 << (w - 1);
        valid_states.push_back(curstate);
        return;
    }
    if (cur + r <= w)
    {
        dfs(cur + r, w, curstate | (1 << (cur + r - 1)), valid_states);
    }
    if (cur + g <= w)
    {
        dfs(cur + g, w, curstate | (1 << (cur + g - 1)), valid_states);
    }
}
int main()
{
    int w, h;

    scanf("%d%d", &w, &h);

    vector<int> valid_states;

    dfs(0, w, 0, valid_states);

    const int m = valid_states.size();

    vector<vector<int>> maps(m);

    for (int i = 0; i < m; ++i)
    {
        for (int j = 0; j < m; ++j)
        {
            if ((valid_states[i] & valid_states[j]) == 0)
            {
                maps[i].push_back(j);
            }
        }
    }

    vector<vector<unsigned long long>> dp(h, vector<unsigned long long>(m, 0));

    for (int i = 0; i < m; ++i)
    {
        dp[0][i] = 1;
    }

    for (int i = 1; i < h; ++i)
    {
        for (int j = 0; j < m; ++j)
        {
            for (int ne : maps[j])
            {
                dp[i][j] += dp[i - 1][ne];
            }
        }
    }

    unsigned long long res = 0;

    for (int i = 0; i < m; ++i)
    {
        res += dp[h - 1][i];
    }

    printf("%llu\n", res);

    return 0;
}

发表于 2022-07-07 15:44:33 回复(0)