P1731 [NOI1999] 生日蛋糕——典型的回溯和剪枝题目,值得一看

今天尝试了一下md的编辑器,不知道有没有什么改变

[NOI1999] 生日蛋糕

题目背景

数据加强版 link

题目描述

7 月 17 日是 Mr.W 的生日,ACM-THU 为此要制作一个体积为 N π N\pi Nπ M M M 层生日蛋糕,每层都是一个圆柱体。

设从下往上数第 i i i 1 ≤ i ≤ M 1 \leq i \leq M 1iM)层蛋糕是半径为 R i R_i Ri,高度为 H i H_i Hi 的圆柱。当 i < M i \lt M i<M 时,要求 R i > R i + 1 R_i \gt R_{i+1} Ri>Ri+1 H i > H i + 1 H_i \gt H_{i+1} Hi>Hi+1

由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积 Q Q Q 最小。

请编程对给出的 N N N M M M,找出蛋糕的制作方案(适当的 R i R_i Ri H i H_i Hi 的值),使 S = Q π S=\dfrac{Q}{\pi} S=πQ 最小。

(除 Q Q Q 外,以上所有数据皆为正整数)

输入格式

第一行为一个整数 N N N N ≤ 2 × 1 0 4 N \leq 2 \times 10^4 N2×104),表示待制作的蛋糕的体积为 N π N\pi Nπ

第二行为 M M M M ≤ 15 M \leq 15 M15),表示蛋糕的层数为 M M M

输出格式

输出一个整数 S S S,若无解,输出 0 0 0

样例 #1

样例输入 #1

100
2

样例输出 #1

68

这个题是一道显然的回溯加剪枝的题目,首先我们要把基础的方法写出来,再来剪枝。所以我们很轻松就可以想到这样的解法:
1.枚举层数(从n到1),半径,高度,体积以及面积,在层数达到0并且体积恰好等于n的时候将面积与面积的最小值进行比较,如果比他小就更改。
2.在函数内部枚举半径i和高度h,范围题目已经告诉你了。
3.当已选中一个高度和一个半径的时候,就按照题目的要求进行下一次的递归。
也就是这样

#include<bits/stdc++.h>
using namespace std;

int n,m,minn=INT_MAX;

void dfs(int step,int r,int h,int s,int v){
	if (step==0&&v==n){
		minn=min(s,minn);
		return;
	}
	for (int i=r-1;i>=step;i--){
		if (step==m)s=i*i;//这个地方很重要,一定要记得将下表面存着
		for (int j=h-1;j>=step;j--){
			dfs(step-1,i,j,s+2*i*j,v+i*i*j);
		}
	}
	return;
}
int main(){
	cin>>n>>m;
	dfs(m,n,n,0,0);
	if (minn==INT_MAX)cout<<0;
	else cout<<minn;
	return 0;
}

接下来就是肯定是剪枝了,怎么剪呢?很容易就可以想到如果当前的面积加上往后最小的面积和都要比最小的面积情况大的话,直接退出,同样又有如果当前的v已经大于了n也是直接退出,再有如果当前的v从而算出来的s也是大于最小的面积的话,也是直接退出。代码如下:

#include<bits/stdc++.h>
using namespace std;

int n,m,minn=INT_MAX;
int mins[30000],minv[30000];

void dfs(int step,int r,int h,int s,int v){
	if (step==0){
		if (v==n)minn=min(s,minn);
		return;
	}
	if (s+mins[step-1]>=minn)return;
	if (v+minv[step-1]>n)return;
	if(2.0*(n-v)/r+s>=minn)  return;//要用小数,不能用整数,要不然要出错
	for (int i=r-1;i>=step;i--){
		if (step==m)s=i*i;
		for (int j=h-1;j>=step;j--){
			dfs(step-1,i,j,s+2*i*j,v+i*i*j);
		}
	}
	return;
}
int main(){
	cin>>n>>m;
	for (int i=1;i<=m;i++){
		mins[i]=mins[i-1]+2*i*i;//记录面积的最小
		minv[i]=i*i*i;//记录体积的最小
	}
	dfs(m,n,n,0,0);
	if (minn==INT_MAX)cout<<0;
	else cout<<minn;
	return 0;
}

那么到了这里,基本上函数根本上的优化是没有了,这时候我们就要考虑一下优化循环了:
半径肯定是不能优化了,那么我们就要考虑一下高度了,能不能优化呢?显然是可以的,我们可以用总的体积减去当前的体积和后面最小的体积和再来除以i²得到后面的h并与当前的h进行比较,从而缩短循环的时间,代码如下:

#include<bits/stdc++.h>
using namespace std;

int n,m,minn=INT_MAX;
int mins[30000],minv[30000];

void dfs(int step,int r,int h,int s,int v){
	int maxh=h;
	if (step==0){
		if (v==n)minn=min(s,minn);
		return;
	}
	if (s+mins[step-1]>=minn)return;
	if (v+minv[step-1]>n)return;
	if(2.0*(n-v)/r+s>=minn)  return;
	for (int i=r-1;i>=step;i--){
		if (step==m)s=i*i;
		maxh=min(h-1,(n-minv[step-1]-v)/i/i);
		for (int j=maxh;j>=step;j--){
			dfs(step-1,i,j,s+2*i*j,v+i*i*j);
		}
	}
	return;
}
int main(){
	cin>>n>>m;
	for (int i=1;i<=m;i++){
		mins[i]=mins[i-1]+2*i*i;
		minv[i]=i*i*i;
	}
	dfs(m,n,n,0,0);
	if (minn==INT_MAX)cout<<0;
	else cout<<minn;
	return 0;
}


看了这么久,作者也写了这么久,能不能点一个赞,在收藏一下呢?最好的话在点个关注吧

谢谢啦!​
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值