有一个n行m列(1<=n,m<=20)的网格蛋糕上有一些樱桃。每次可以用一刀沿着网格线把蛋糕切成两块,并且只能够直切不能拐弯。要求最后每一块蛋糕上恰好有一个樱桃,且切割线总长度最小。
刚看到这道题的时候因为有了前两题的经验就往记忆化搜索那边靠,一下就想到了搜索思路:dp[x][y][xlen][ylen]表示以(x,y)为起点的分别以xlen,ylen为边长的矩形区域的最优解,这样就可以横着切每条网格线和纵着切每条网格线,对于每个搜索到的区域,如果之前没访问过,那么首先要判断此区域是否存在樱桃,有几个,如果没有则可以直接返回一个极大值抛弃这种情况,如果只有一个那就是末状态了,返回0,表明这一块的最小值为0,如果有多于1个,继续切割,每次转移时考虑切下来的两块的最优值加上切这一刀的花费。
转移方程:dfs(x,y,xlen,ylen)=min(dfs(x,y,xlen,ylen),dfs(x,y,i-x+1,ylen)+ylen+dfs(i+1,y,xlen-(i-x+1),ylen),dfs(x,y,xlen,j-y+1)+xlen+dfs(x,j+1,xlen,ylen-(j-y+1)));
tips:在dfs时可以用一个引用代表dp[][][][],既可省力又能使代码美观
#include
#include
#include
#include
using namespace std;
#define N 25
int m,n,k,dp[N][N][N][N],map[N][N],kase=0;
//dp(x,y,xlen,ylen)表示在以(x,y)为左上角,xlen,ylen为边长的区域内的切割最小值
void init(){
memset(map,0,sizeof(map));
for(int i=1;i<=m;i++)//初始化dp数组为无穷大
for(int j=1;j<=n;j++)
for(int o=1;o<=m;o++)
for(int p=1;p<=n;p++)
dp[i][j][o][p]=1000;
int x,y;
for(int i=1;i<=k;i++){
cin>>x>>y;//记录cherry位置
map[x][y]=1;
}
}
int dfs(int x,int y,int xlen,int ylen){//区域的左上角坐标(x,y),区域边长xlen和ylen
int& d = dp[x][y][xlen][ylen];
if(d!=1000) return d;//已经查找过的就直接返回
int cut=0;
for(int i=x;i<=x+xlen-1;i++)
for(int j=y;j<=y+ylen-1;j++)
if(map[i][j]) cut++;//判断此区域内有多少cherry
if(cut)
if(cut==1){//只有一个就直接返回
return d=0;
}else{//有一个以上就要继续切
for(int i=x;i<=x+xlen-2;i++)//横着切x~x+xlen-2
d=min(d,dfs(x,y,i-x+1,ylen)+ylen+dfs(i+1,y,xlen-(i-x+1),ylen));
for(int j=y;j<=y+ylen-2;j++)//纵向切y~y+ylen-2
d=min(d,dfs(x,y,xlen,j-y+1)+xlen+dfs(x,j+1,xlen,ylen-(j-y+1)));
}
else//没有就舍弃这种情况
return d=2000;
return d;
}
int main(){
//freopen("2.txt","w",stdout);
while(cin>>m>>n>>k){
init();
cout<< "Case " << ++kase << ": " <1,1,m,n)<return 0;
}
开始做动态规划以来最流畅的一题,思路清晰,而且一次AC,爽快O(∩_∩)O~
ヽ(✿゚▽゚)ノ✧(≖ ◡ ≖✿)