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外,以上所有數據皆爲正整數)

思路分析

本題首先需要解決的一個問題是,這個多層蛋糕的表面積應該如何計算,注意下層比上層多出來的這部分也是需要塗抹奶油的。這裏我們運用一個技巧,從頂向下看,所有多出來的部分組成了一個大圓,這個大圓就是底層蛋糕的底面積,因此蛋糕的表面積記爲
S=R12H1+2H1R1+...+2HnRnst.Hi>hi+1,Ri>Ri+1S=R_1^2H_1+2H_1R_1+...+2H_nR_n\\st. H_i>h_{i+1},R_i>R_{i+1}
在這裏插入圖片描述
我們不妨從底層向頂層搜索,搜索對象是每一層的高度和半徑,函數大概長這樣

//到了第l層,還剩V的體積,現在一共S的表面積,上一層的高度和半徑爲h,r
void dfs(int l, int V, int S, int h, int r) 

那麼對於第一層比較特別,因爲第一層決定了表面積SS中的R12H1+2H1R1R_1^2H_1+2H_1R_1兩部分,而且沒有底層的限制,我們不妨把他單獨拿出來單獨確定狀態

		//確定底層的狀態,從底向上搜索
		for (int r = sqrt(N); r >= M; r--)
			for (int h = N / p[r]; h >= M; h--)
				dfs(2, N - p[r] * h, p[r] + 2 * r*h, h, r);

那麼在確定了上一層l1l-1的高度hh和半徑rr後,對ll層我們應該是這樣搜索的:

	//遍歷可能的半徑和高度,第l層半徑和高度一定大於M-l
	for (int i = h - 1; i >= M - l + 1; i--)
		for (int j = r - 1; j >= M - l + 1; j--)
			dfs(l + 1, V - i * j*j, S + 2 * i*j, i, j);

這裏其實是一個隱形的剪枝,第l層半徑和高度一定大於M-l。當搜索到M+1M+1層時,我們的搜索結束了,如果此時恰好用光了所有體積,那麼這是一種可行的方案,可以更新答案

	if (l == M + 1) {
		if (S < res && V == 0)res = S;
		return;
	}

坐到這裏這道題是會TLE的,我們需要幾個優秀的剪枝策略,剪枝利用的是極限的思想,而在這道題裏,當上一層的狀態確定了後,我們可以確定接下來幾層最多使用多少體積MAXVMAXV,和至少使用多少體積MINVMINV,如果體積比最多的還多或者比最少的要少,那麼這個狀態就可以剪掉了。

//在第l層用了高h,半徑r的蛋糕,接下來至少用多少體積
int minV(int h, int r, int l) {
	if (r <= M - l || h <= M - l)return inf;//高和半徑不夠接下來每層都比這裏小
	else return vs[M - l];//剩下三層的話,第一層1*1*1,。。。,第三層3*3*3
}

//在第l層用了高h,半徑r的蛋糕,接下來最多用多少體積
int maxV(int h, int r, int l) {
	if (r <= M - l || h <= M - l)return 0;
	int sum = 0;
	for (int i = l + 1; i <= M; i++)
		//上面每一層比下面僅僅多1
		sum += (h + i - l)*(r + i - l)*(r + i - l);
	return sum;
}

最後,如果當前存儲的結果值SS已經比我們已有的答案大了,那麼結果一樣可以減掉

	if (S > res)return;//結果不可能更好
	if (V < minV(h, r, l - 1))return;//體積不夠
	if (V > maxV(h, r, l - 1))return;//剩的太多
#include<iostream>
#include<string>
#include<string.h>
#include<cstdio>
#include<algorithm>
using namespace std;

#define inf 0x3f3f3f3f
#define MAX 105

int N, M, res, p[MAX], h[MAX], r[MAX], vs[MAX];

//在第l層用了高h,半徑r的蛋糕,接下來至少用多少體積
int minV(int h, int r, int l) {
	if (r <= M - l || h <= M - l)return inf;//高和半徑不夠接下來每層都比這裏小
	else return vs[M - l];//剩下三層的話,第一層1*1*1,。。。,第三層3*3*3
}

//在第l層用了高h,半徑r的蛋糕,接下來最多用多少體積
int maxV(int h, int r, int l) {
	if (r <= M - l || h <= M - l)return 0;
	int sum = 0;
	for (int i = l + 1; i <= M; i++)
		//上面每一層比下面僅僅多1
		sum += (h + i - l)*(r + i - l)*(r + i - l);
	return sum;
}

//到了第l層,還剩V的體積,現在一共S的表面積,上一層的高度和半徑爲h,r
void dfs(int l, int V, int S, int h, int r) {
	if (l == M + 1) {
		if (S < res && V == 0)res = S;
		return;
	}
	if (S > res)return;//結果不可能更好
	if (V < minV(h, r, l - 1))return;//體積不夠
	if (V > maxV(h, r, l - 1))return;//剩的太多

	//遍歷可能的半徑和高度,第l層半徑和高度一定大於M-l
	for (int i = h - 1; i >= M - l + 1; i--)
		for (int j = r - 1; j >= M - l + 1; j--)
			dfs(l + 1, V - i * j*j, S + 2 * i*j, i, j);
}

int main() {
	vs[0] = 0;
	for (int i = 1; i < MAX; i++)
		p[i] = i * i, vs[i] = p[i] * i + vs[i - 1];//預處理一波平方項,立方和

	while (scanf("%d %d", &N, &M) != EOF) {
		res = inf;
		int id = lower_bound(p, p + MAX, N) - p;

		//確定底層的狀態,從底向上搜索
		for (int r = id; r >= M; r--)
			for (int h = N / p[r]; h >= M; h--)
				dfs(2, N - p[r] * h, p[r] + 2 * r*h, h, r);
			
		
		if (res == inf)cout << 0 << endl;
		else cout << res << endl;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章