poj1190详细题解

题目大意:输入一个蛋糕的最大体积和最大层数
然后对于不同的半径和高计算出最小表面积
最小表面积有则打印最小表面积,没有则打印0
蛋糕的(从下往上数)下面一层比上面一层的半径和高要大,因为都是整数
除了Q都是整数

简洁版见最后面

解析版

//Memory 108K Time 0MS
#include <iostream>
#include <cstdio>

using namespace std;

    //蛋糕的最大体积  蛋糕的最大层数  遍历蛋糕的当前层数  目前已经积累的表面积
    //目前已经积累的体积  当前层数上一层的半径  当前层数上一层的高  最小表面积
void dfs(int N, int M, int nowIndex, int sumS, int sumV, int preR, int preH, int &minValue) {
    //设最开始遍历的蛋糕层数为最上层,即nowIndex=M
    //当遍历到的层数等于0时,说明已经到达叶子结点
    // 则进一步判断该可行解的表面积,是否比之前查找到的最小表面积更小
     //若当前表面积更小则更新minValue,反之不更新
    if (nowIndex == 0) {
        if (sumV == N)
            minValue = (sumV == N && minValue > sumS ? sumS : minValue);
    } else {
        //当未到达叶子结点
        //则对每层蛋糕的半径和高进行枚举
        //由题可知蛋糕的当前遍历层的半径上界是下面一层的半径减一   
//              *
 //            * *
   //         * * *
    //       * * * * 
        //而当前遍历层的半径下界是当前层数  (举个例子:若目前存在一个四层的蛋糕,假设最顶层至最底层的半径依次为1,2,3,4)
        //此时我们按从上往下数,依次为第一层第二层第三层第四层.
        //此时每层都按最小值取的半径,
        //是不是就是第一层的最小半径为1,第二层的最小半径为2
        //第三层的最小半径为3,第四层的最小半径为4
        //则可以得到当前层的最小半径等于当前的层数(从上往下数,第一层第二层第三层第四层...)
        //则最顶层蛋糕的最小半径则为1(从最上面一层开始往下数1,2,3,4),所以顶层为1,最大为3,第二层半径减一:(4-1)
        //则此时,我们假设是从下往上遍历蛋糕的每一层,并且假设在第一层(从下往上数)
        //虚拟一个半径和高,那么我们就可以得到第一层(从下往上数)的最大半径和最小半径
        //依次类推第二层,第三层...(从下往上数)
        //要满足上述的构思
        //则我们需要在传入第一次dfs参数时,传入的第三个参数(见dfs函数)则为对应的,我们在一开始就录入的层数
        //高同理可得, 因为题目的限制,半径和高均为整数
        
        //枚举完各层的半径和高后,就可以使得每层都有一个相应的半径和高进行侧面积和体积的计算
        //剪枝分为3步
        //1.若当前累计的体积大于蛋糕的最大体积则剪枝,因为题目限制可行解必须最终体积等于最大体积
        //2.若当前累计的表面积大于当前已经得到的最小表面积,则剪枝,因为当前分支继续往下走都不会比之前找到的最优解(最小表面积)更好
        // 3.若当前累计的表面积加上当前分支后面的表面积比之前找到最优解(最小表面积)大,则剪枝
        //(N-sumV)*2/preR  ->  可以看作是V=pi*r^2*h和S=2*r*h  推导出来的公式   S=2V/r
        //(N-sumV)--剩余的体积  即  最大体积减去当前累计的体积
        //(N-sumV)*2/preR+sumS  即(最大可累计的剩余体积)对应的表面积加上已经累计的表面积
        if (sumV > N || sumS > minValue || (N - sumV) * 2 / preR + sumS > minValue)
            return;
        
        //枚举每层蛋糕可能的半径和高
        //从最大值枚举至最小值
        for (int r = preR - 1; r >= nowIndex; r--)
            for (int h = preH - 1; h >= nowIndex; h--) {
                //当当前层数nowIndex等于M时
                //即当前遍历的层数为最顶层,所以表面积的计算转变为s=pi*r*r
                //蛋糕的总表面积即最底层的面积加上每一层的侧面积
                //从上往下看蛋糕,即每层的上表面正好构成一个圆,这个圆的半径则为最底层的半径
                sumS = (nowIndex == M ? r * r : sumS);
                //nowIndex-1  从顶层向下遍历每一层蛋糕
                dfs(N, M, nowIndex - 1, sumS + 2 * r * h, sumV + r * r * h, r, h, minValue);
            }
    };
}

int main() {
    int N, M;
    //录入蛋糕的最大体积和蛋糕的最大层数
    scanf("%d%d", &N, &M);
    //最小表面积  会在dfs中更新,若没有更新则输出0
    int minSurfaceArea = 10000;
    //蛋糕的最大体积  蛋糕的最大层数  遍历蛋糕的当前层数  目前已经积累的表面积
    //目前已经积累的体积  当前层数上一层的半径  当前层数上一层的高  最小表面积
    dfs(N, M, M, 0, 0, 26, 26, minSurfaceArea);
    printf("%d\n", minSurfaceArea = (minSurfaceArea < 10000) ? minSurfaceArea : 0);

    return 0;
}

另一种思路
先求解出每一层的理论最小体积和最小表面积
然后计算出第一层(从下往上数)的理论最大半径和高
将这个半径和高传递给dfs

//Memory 112K Time 16MS
#include <iostream>
#include <cstdio>
#include <cmath>

using namespace std;
int N, M;
int *minV;
int *minS;
int minSArea;

void ini(int *minV, int *minS) {
    minV[0] = 0, minS[0] = 0;
    for (int i = 1; i <= M; i++) {
        minV[i] = minV[i - 1] + i * i * i;
        minS[i] = minS[i - 1] + 2 * i * i;
    }
}

int maxV(int M, int r, int h) {
    int v = 0;
    //遍历每层的最大半径和高
    //计算出蛋糕的最大体积
    //每层蛋糕的(自底向上)半径和高的最大值为上一层减1
    for (int i = 0; i < M; i++) {
        v += r * r * h;
        r--;
        h--;
    }
    return v;
}

void dfs(int nowIndex, int v, int r, int h, int area) {
    //自底向上,nowIndex最开始传入的是M也就是最大层数
    //当nowIndex为0并且剩余体积为0
    //说明取得一个可行解
    //则进行最小表面积的比较
    //看是否需要更新
    if (nowIndex == 0 && v == 0)
        minSArea = min(minSArea, area);
    else {
        //剪枝条件有7个
        //1.当剩余体积小于0,说明此时已经用完了最大的体积N,在往下查找已经不可能找到可行解了
        //2.当前层数nowIndex(自底向上的所有层数包括nowIndex层)最小体积已经大于N,说明在往下查找也不可能找到可行解了
        //3.已经积累的表面积加上剩余未遍历层数的表面积大于之前找到的最优解minSArea,则无需继续查找
        //4.当前遍历层的半径小于了理论当前层的最小半径
        //5.当前遍历层的高度小于了理论当前层的最小高度
        //6.在目前给定的最底层半径和高度的条件下,构造的蛋糕最大体积都小于目标体积N
        //7.剩余体积(N-已经积累的体积)转化的表面积加上已经积累的表面积大于之前找到的最优解的minSArea
        //7和3不一样
        if (v <= 0 || minV[nowIndex] > v || area + minS[nowIndex] > minSArea || r < nowIndex || h < nowIndex ||
            maxV(nowIndex, r, h) < v || v * 2 / r + area > minSArea)
            return;
         //半径和高都从最大值向最小值开始遍历
         //半径和高的上界和下界上面已经介绍过了
         //就不再次说明了
        for (int i = r; i >= nowIndex; i--) {
            for (int j = h; j >= nowIndex; j--) {
                area += nowIndex == M ? (i * i) + (2 * i * j) : 2 * i * j;
                dfs(nowIndex - 1, v - i * i * j, i - 1, j - 1, area);
                area -= nowIndex == M ? (i * i) + (2 * i * j) : 2 * i * j;
            }
        }
    }

}

//另一种思路
//先求解出每一层的理论最小体积和最小表面积
//然后计算出第一层(从下往上数)的理论最大半径和高
//将这个半径和高传递给dfs
int main() {
    scanf("%d%d", &N, &M);
    minV = new int[M + 1];
    minS = new int[M + 1];
    //1的二进制取值
    //左移12位,遇0乘2
    //则为2^12=4096
    minSArea = 1 << 12;
    ini(minV, minS);
    //构造的最大层数
    //理论最小体积都小于N
    if (minV[M] > N)
        printf("0\n");
    else {
        //为节省时间,可以使用直接定义最大半径和高度,测试得到最大的半径和高度,可以通过测试的
        //半径和高都设置为26,可以实现0MS
        int maxR = (int) sqrt(double(N - minV[M - 1]) / M);
        int maxH = N - minV[M - 1] / (M * M);
        dfs(M, N, maxR, maxH, 0);
        printf("%d\n", minSArea == 1 << 12 ? 0 : minSArea);
    }
    delete[] minV;
    delete[] minS;
    return 0;
}

简洁版

//Memory 108K Time 0MS
#include <iostream>
#include <cstdio>

using namespace std;

void dfs(int N, int M, int nowIndex, int sumS, int sumV, int preR, int preH, int &minValue) {
    if (nowIndex == 0) {
        if (sumV == N)
            minValue = (sumV == N && minValue > sumS ? sumS : minValue);
    } else {
        if (sumV > N || sumS > minValue || (N - sumV) * 2 / preR + sumS > minValue)
            return;

        for (int r = preR - 1; r >= nowIndex; r--)
            for (int h = preH - 1; h >= nowIndex; h--) {
                sumS = (nowIndex == M ? r * r : sumS);
                dfs(N, M, nowIndex - 1, sumS + 2 * r * h, sumV + r * r * h, r, h, minValue);
            }
    };
}

int main() {
    int N, M;
    scanf("%d%d", &N, &M);
    int minSurfaceArea = 10000;
    dfs(N, M, M, 0, 0, 26, 26, minSurfaceArea);
    printf("%d\n", minSurfaceArea = (minSurfaceArea < 10000) ? minSurfaceArea : 0);

    return 0;
}

//Memory 112K Time 16MS
#include <iostream>
#include <cstdio>
#include <cmath>

using namespace std;
int N, M;
int *minV;
int *minS;
int minSArea;

void ini(int *minV, int *minS) {
    minV[0] = 0, minS[0] = 0;
    for (int i = 1; i <= M; i++) {
        minV[i] = minV[i - 1] + i * i * i;
        minS[i] = minS[i - 1] + 2 * i * i;
    }
}

int maxV(int M, int r, int h) {
    int v = 0;
    for (int i = 0; i < M; i++) {
        v += r * r * h;
        r--;
        h--;
    }
    return v;
}

void dfs(int nowIndex, int v, int r, int h, int area) {
    if (nowIndex == 0 && v == 0)
        minSArea = min(minSArea, area);
    else {
        if (v <= 0 || minV[nowIndex] > v || area + minS[nowIndex] > minSArea || r < nowIndex || h < nowIndex ||
            maxV(nowIndex, r, h) < v || v * 2 / r + area > minSArea)
            return;
        for (int i = r; i >= nowIndex; i--) {
            for (int j = h; j >= nowIndex; j--) {
                area += nowIndex == M ? (i * i) + (2 * i * j) : 2 * i * j;
                dfs(nowIndex - 1, v - i * i * j, i - 1, j - 1, area);
                area -= nowIndex == M ? (i * i) + (2 * i * j) : 2 * i * j;
            }
        }
    }

}


int main() {
    scanf("%d%d", &N, &M);
    minV = new int[M + 1];
    minS = new int[M + 1];
    minSArea = 1 << 12;
    ini(minV, minS);
    if (minV[M] > N)
        printf("0\n");
    else {
        int maxR = (int) sqrt(double(N - minV[M - 1]) / M);
        int maxH = N - minV[M - 1] / (M * M);
        dfs(M, N, maxR, maxH, 0);
        printf("%d\n", minSArea == 1 << 12 ? 0 : minSArea);
    }
    delete[] minV;
    delete[] minS;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值