【UVa】【DP】1629 Cake Slicing

本文分析了UVa1629 Cake Slicing问题,通过记忆化搜索的方法解决如何将带有樱桃的矩形蛋糕切成若干块,使得每块恰好包含一个樱桃,并计算最小切割成本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UVa 1629 Cake Slicing

题目

◇题目传送门◆(P.S.由于UVa较慢,这里提供一个vjudge的链接)
◇题目传送门(vjudge)◆

题目大意

给定 N,M N , M ,表示一个矩形蛋糕的长宽。蛋糕上面有 K K 个樱桃,每个樱桃都在一个单位蛋糕上。现需要将这个蛋糕分成若干块,使每块上面有且只有一个樱桃。若每刀只能沿着网格线切,且切一刀蛋糕的花费为与这一刀平行的边的长度,求最小花费。(数据保证有解)

思路

这道题似乎比较难写出状态转移方程,所以,我们可以采用记忆化搜索来做。

定义状态f[x1][y1][x2][y2]为左上角坐标为 (x1,y1) ( x 1 , y 1 ) (相对于原蛋糕的左上角),且右下角坐标为 (x2,y2) ( x 2 , y 2 ) 的蛋糕达到目标的最小花费。

我们在记忆化搜索的时候就可以枚举切的位置,暴力找出答案。

这时我们注意到,若反复查找当前蛋糕中的樱桃个数,则可能会超时。

如何解决这个问题?

我们定义 c[x][y] c [ x ] [ y ] 为一个以左上角为原蛋糕左上角,右下角坐标为 (x,y) ( x , y ) 的蛋糕中的樱桃数量,计算时可以采用递推计算,具体见代码。

在计算左上角坐标为 (x1,y1) ( x 1 , y 1 ) (相对于原蛋糕的左上角),且右下角坐标为 (x2,y2) ( x 2 , y 2 ) 的蛋糕中的樱桃数量时,可以使用容斥原理。举个例子:
这里写图片描述
如上图,我们计算红色部分的樱桃数量时,可先用总数(即 c[x2][y2] c [ x 2 ] [ y 2 ] ),减掉两个紫色部分的数量,即 c[x11][y2],c[x2][y11] c [ x 1 − 1 ] [ y 2 ] , c [ x 2 ] [ y 1 − 1 ] (为什么要减一?因为红色部分包含了 x1,y1 x 1 , y 1 这两个部分)。此时我们可以发现,阴影部分被减了两次,所以我们需要加上这一部分,即加上 c[x11][y11] c [ x 1 − 1 ] [ y 1 − 1 ] (减一的原因请见上文)。

所以,我们就可以在 O(1) O ( 1 ) 的时间计算出当前蛋糕的樱桃数量。

更多细节详见代码。

正解代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int Maxn=20;
const int INF=0x3f3f3f3f;

int N,M,K,A[Maxn+5][Maxn+5];
int cnt[Maxn+5][Maxn+5];
int f[Maxn+5][Maxn+5][Maxn+5][Maxn+5];

int DFS(int x1,int y1,int x2,int y2) {
    int &res=f[x1][y1][x2][y2];
    //定义引用,方便操作
    if(res!=-1) return res;
    int tmp=cnt[x2][y2]-cnt[x1-1][y2]-cnt[x2][y1-1]+cnt[x1-1][y1-1];
    //计算当前樱桃数量
    if(tmp==1) return 0;
    //已经到达目标则返回0
    if(tmp==0) return INF;
    //该蛋糕上没有樱桃,不合法,故返回INF
    res=INF;
    for(int i=y1;i<y2;i++)
        res=min(res,DFS(x1,y1,x2,i)+DFS(x1,i+1,x2,y2)+x2-x1+1);
    //竖着切
    for(int i=x1;i<x2;i++)
        res=min(res,DFS(x1,y1,i,y2)+DFS(i+1,y1,x2,y2)+y2-y1+1);
    //横着切
    return res;
}

int main() {
    #ifdef LOACL
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    int cas=0;
    while(scanf("%d %d %d",&N,&M,&K)!=EOF) {
        for(int i=1;i<=K;i++) {
            int x,y;
            scanf("%d %d",&x,&y);
            A[x][y]=1;
        }
        for(int i=1;i<=N;i++)
            for(int j=1;j<=M;j++)
                cnt[i][j]=cnt[i-1][j]+cnt[i][j-1]-cnt[i-1][j-1]+A[i][j];
        //递推计算c数组
        memset(f,-1,sizeof f);//注意初值为-1
        printf("Case %d: %d\n",++cas,DFS(1,1,N,M));
        memset(cnt,0,sizeof cnt);
        memset(A,0,sizeof A);//清空所有数组
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值