POJ 1190 DFS+剪枝

描述
7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体。
设从下往上数第i(1 <= i <= M)层蛋糕是半径为Ri, 高度为Hi的圆柱。当i < M时,要求Ri > Ri+1且Hi > Hi+1。
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。
令Q = Sπ,请编程对给出的N和M,找出蛋糕的制作方案(适当的Ri和Hi的值),使S最小。
(除Q外,以上所有数据皆为正整数)
1000MS10000KB
输入
有两行,第一行为N(N <= 10000),表示待制作的蛋糕的体积为Nπ;第二行为M(M <= 20),表示蛋糕的层数为M。
输出
仅一行,是一个正整数S(若无解则S = 0)。
样例输入
100
2
Sample Output
68
圆柱公式
体积V = πR 2H
侧面积A’ = 2πRH
底面积A = πR 2
杂记
2020年3月4日,我接触此题,并为这道题花了整整一天的时间来做,从纠结于用BFS还是DFS,一开始还是想的是D,后来看到数据量有点大,就像是不是应该用BFS,最后DFS和BFS都写了,都用了剪枝,就是跟求极限情况。如果这一层的所选的半径和高度所造成的最小体积+现在所有的体积都大于N就减去。我这天都没意识到一个问题就是搜索的顺序,我一开始是从上往下搜的,觉得第一层半径和高度都从1开始,毕竟有个清晰的起点,也很自信的搜下去了。确实这个剪枝能剪掉一部分情况,后来上网搜索了这道题,又发现可以最优性剪枝,如果当前层所产生的最小表面积加上现有的表面积都大于ans,就剪掉。确实,加上这个剪枝后,10000 的3,4 ,5都很快出来了,还不够。我实在想不到还能怎么优化,就有上网查了这道题,发现还有一个很强的剪枝,说如果当前剩余体积整个做成一个圆柱后,这样增加的表面积是最小的,但这是对于从下往上构造的情况才有效,我从上往下构造蛋糕,口径不断变大,高度不断增加,如果要修改最上层的话,回溯太麻烦了。
正确思路
知道真相的我眼泪掉下来。。应改从下往上搜,起始半径为sqrt(N)+1,起始高度应该为N+1,这是很大的值,因为一开始肯定有很多值不满足条件,最下面一层的高度太高,那么加起来的最小体积超N,或者半径太大,最小体积超N。这一层的R和H就不断往小走,如果H太小就说明够不到N,具体看注释把
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;

int n, m, q;
int ans;

int minv[22];
int leftS[22];

#define DEBUG

void dfs(int ci,int nv,int ns,int r,int h) {
	#ifdef DEBUG
		//printf("ci=%d nv=%d ns=%d nr=%d nh=%d\n", ci, nv, ns,r,h);
	#endif // DEBUG


	if (ci == 0 &&nv==n) {
		ans = min(ans, ns);
		return;
	}

	if (leftS[ci] + ns >= ans) return;
	if (minv[ci] + nv > n) return;

	int mrv;
	int nr, nh;
	int avgV = (n - nv) / ci;//得到关于当前剩余层数和剩余体积的层平均体积
	for (nr = r - 1; nr>=ci; nr--) {
		for (nh = h - 1; nh>=ci; nh--) {
			int curV = nr*nr*nh;//当前层的体积
			if (curV < avgV) break;//如果当前层体积小于层平均体积,那么由于上面的层体积一定比这层小,那么加起来也不能够到总体积
			if (curV > n-nv) continue;//h选大了,要缩小
			mrv = 0;//从第ci+1层到第1层将产生的最大体积,也就是相邻蛋糕半径和高度差1
			for (int i = 1; i<ci; i++) {
				mrv += (nr  - i)*(nr  - i)*(nh - i);
			}
			if (n - nv - curV > mrv) break;//意思就是说第ci层选的半径和高度按最大体积的方式堆到顶的体积和都达不到n的话
			if (ci == m) ns = nr*nr;//如果当前层是最底层 就要先加上底层圆的面积

			dfs(ci - 1, nv + curV, ns + 2 * nr* nh,nr,nh);//递归下一层
		}
	}
	
}

int main() {
	for (int j = 1; j <= 20; j++) {
		for (int i = 0; i < j; i++) {
			leftS[j] += (j - i)*(j - i);//1 5
			minv[j] += (j - i)*(j - i)*(j - i);//1 9 36 100
		}
	}
	while (scanf("%d %d", &n, &m) != EOF) {
		ans = 1e9;
		dfs(m, 0, 0, sqrt((double)n)+1, n+1);
		printf("%d\n", ans==1e9?0:ans);
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值