小军接到一项工作,要用红色和绿色两种砖块建一面宽度W高度H的墙
小军只有以下两种砖块可以用:
红色砖块宽度2,高度1
绿色砖块宽度3,高度1
每种砖块只能按照以上的横向摆放,不能竖起来
横向两块方块之间是有缝的,因此为了让建好墙更稳固,小军会避免在任意相邻的两行间,除了左右两侧以外,出现任何上下对齐的砖缝,请参看下图:
给定一面墙的宽度和高度后,请帮小军计算在以上规则允许的前提下,一共有多少种不同的建墙方式
小军接到一项工作,要用红色和绿色两种砖块建一面宽度W高度H的墙
小军只有以下两种砖块可以用:
红色砖块宽度2,高度1
绿色砖块宽度3,高度1
每种砖块只能按照以上的横向摆放,不能竖起来
横向两块方块之间是有缝的,因此为了让建好墙更稳固,小军会避免在任意相邻的两行间,除了左右两侧以外,出现任何上下对齐的砖缝,请参看下图:
给定一面墙的宽度和高度后,请帮小军计算在以上规则允许的前提下,一共有多少种不同的建墙方式
两个整数 W 和 H (2 <= W <= 30, 1 <= H <= 10)
一个整数,所有可能建墙方式的数量。这个数量可能会大于32位整数的表示范围,请用64位整数
5 2
2
9 5
14
#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;
} #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)
这套题真的难的离谱,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)
}
#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;
}