題目大意
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外,以上所有數據皆爲正整數)
思路分析
本題首先需要解決的一個問題是,這個多層蛋糕的表面積應該如何計算,注意下層比上層多出來的這部分也是需要塗抹奶油的。這裏我們運用一個技巧,從頂向下看,所有多出來的部分組成了一個大圓,這個大圓就是底層蛋糕的底面積,因此蛋糕的表面積記爲
我們不妨從底層向頂層搜索,搜索對象是每一層的高度和半徑,函數大概長這樣
//到了第l層,還剩V的體積,現在一共S的表面積,上一層的高度和半徑爲h,r
void dfs(int l, int V, int S, int h, int r)
那麼對於第一層比較特別,因爲第一層決定了表面積中的兩部分,而且沒有底層的限制,我們不妨把他單獨拿出來單獨確定狀態
//確定底層的狀態,從底向上搜索
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);
那麼在確定了上一層的高度和半徑後,對層我們應該是這樣搜索的:
//遍歷可能的半徑和高度,第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。當搜索到層時,我們的搜索結束了,如果此時恰好用光了所有體積,那麼這是一種可行的方案,可以更新答案
if (l == M + 1) {
if (S < res && V == 0)res = S;
return;
}
坐到這裏這道題是會TLE的,我們需要幾個優秀的剪枝策略,剪枝利用的是極限的思想,而在這道題裏,當上一層的狀態確定了後,我們可以確定接下來幾層最多使用多少體積,和至少使用多少體積,如果體積比最多的還多或者比最少的要少,那麼這個狀態就可以剪掉了。
//在第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;
}
最後,如果當前存儲的結果值已經比我們已有的答案大了,那麼結果一樣可以減掉
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;
}
}