一种积木搭建方式,高为H的积木,最底层有M个积木,每一层的积木数是他的低一层的积木数+1或-1。总共有N个积木。(且每行积木数不超过10)
比如下图N=13 H=6 M=2。
1
2
3
2
3
2
输入描述 Input Description
第一行为三个整数、N、H、M。
第二行以后每行一个整数K,-1为结束符。
输出描述 Output Description
第一行为满足N、H、M的积木搭建方案总数(1<=N<=540 H<=60 M<=10)
以后每一行对于对应的K,给出顺序排列的第K种方案(最小的排列为第一种)。
(如样例中,2 1 2 3 2 3是一种方案,代表一层的积木分别为212323,232321也是一种方案,212323比232321要小,每个状态之间是可比的,第一个数小的排前面,第一个数相等的就看第二个数。那么所有方案就有一个顺序了,这里的K就是求第K个按顺序排列的方案)
样例输入 Sample Input
13 6 2
1
3
-1
样例输出 Sample Output
3
2 1 2 3 2 3
2 3 2 3 2 1
题目分析:
本题从数据范围来看每层可以扩展2个节点,最多60层,可能达到2^60,直接搜索加上重复计算,再加上数据记录,应该超时,
考虑到每种状态可以扩展两种状态,或者每种状态都只能由两种状态扩展而来,具有最优子结构,可以用动态规划由底层到上层计算。
假设当前层为第h层,从第一层向上递推,结束点为f[h][m][m]
f[hi][ni][mi]代表第hi层,总数积木为ni ,当前层积木的个数为mi,从底层向上动态规划
开始的边界条件为 f[1][i][i]=1;
f[hi][ni][mi]=f[hi-1][ni-mi][mi-1]+f[ni][ni-mi][mi+1]
根据字典序,先扩展mi-1节点。
查找第k个方案的时候,根据节点左右关系,比左节点小向左找,否则向右找。
#include<iostream>
#include<stdio.h>
using namespace std;
int n,h,m;
long long f[62][542][12]={0},k;//f[h][n][m]高度,总数,当前层积木个数
void dp(){
int hi,ni,mi;
for(mi=1;mi<=10;mi++)f[1][mi][mi]=1;
for(hi=2;hi<=h;hi++)//h
for(ni=hi+1;ni<=n;ni++)//n
for(mi=1;mi<=10;mi++)//m
if(ni-mi>=1){
f[hi][ni][mi]+=f[hi-1][ni-mi][mi-1];
if(mi<=9)f[hi][ni][mi]+=f[hi-1][ni-mi][mi+1];
}
}
void find(long long x,int hi,int ni,int mi){
printf("%d ",mi);
if(hi>=2)
if(x<=f[hi-1][ni-mi][mi-1])
find(x,hi-1,ni-mi,mi-1);
else
find(x-f[hi-1][ni-mi][mi-1],hi-1,ni-mi,mi+1);
}
int main(){
cin>>n>>h>>m;
dp();
cout<<f[h][n][m]<<endl;
do{
cin>>k;
if(k==-1)break;
find(k,h,n,m);
printf("\n");
}while(k!=-1);
return 0;
}
同理:也可以用记忆化化搜索,自定向下分拆,递归
6 13 2,两个分支:5 11 1, 5 11 3,先分解,计算,然后返回值。
注意1.已经计算过的顶点直接返回,做题的时候,直接用了f[][][]是否大于0判断记录,调试时候发现错了,需要另外定义一个vis,否则计算结果为0的节点将重复计算。
注意2:不可能的分支及时剪枝,原来只在两次dfs哪里小剪枝,很慢。重新思考后发现剪枝条件,ni/hi>10,ni/hi<1就不可能了,剪掉后很快,dp12ms ,搜索8ms。
#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
int n,h,m;
bool vis[62][542][12]={0};
long long f[62][542][12]={0},k;//h,n.m
long long dfs(int hi,int ni,int mi){
if(ni/hi>10||ni/hi<1)return f[hi][ni][mi]=0;
if(vis[hi][ni][mi])return f[hi][ni][mi];
else{
if(hi==1){
vis[hi][ni][mi]=true;
if(ni==mi&&mi<=10)
return f[hi][ni][mi]=1;
else
return f[hi][ni][mi]=0;
}
if(hi>=2){
long long t=0;
if(mi-1>=1)
t+=dfs(hi-1,ni-mi,mi-1);
if(mi+1<=10)
t+=dfs(hi-1,ni-mi,mi+1);
vis[hi][ni][mi]=true;
return f[hi][ni][mi]=t;
}
}
}
void find(long long x,int hi,int ni,int mi){
printf("%d ",mi);
if(hi>=2)
if(x<=f[hi-1][ni-mi][mi-1])
find(x,hi-1,ni-mi,mi-1);
else
find(x-f[hi-1][ni-mi][mi-1],hi-1,ni-mi,mi+1);
}
int main(){
cin>>n>>h>>m;
dfs(h,n,m);
cout<<f[h][n][m]<<endl;
do{
cin>>k;
if(k==-1)break;
find(k,h,n,m);
printf("\n");
}while(k!=-1);
return 0;
}
本文介绍了一种基于动态规划和记忆化搜索的积木搭建算法,用于寻找满足特定条件的积木堆叠方案总数及其具体排列组合。
5万+

被折叠的 条评论
为什么被折叠?



