NOIP1999 生日蛋糕
NOI原题:
dfs剪枝的经典例题(据yxc老师说他那时北大考了不少次)。
重要一点:题目为了方便计算选择了不考虑π。
剪枝(方便说明,最上面是第一层,当前是第u层,最下面是第m层)
①:优化搜索顺序
由于我们是寻找分支最少的路线进行搜索,所以我们每次应当从下往上搜,并且从当前能取得的最大值搜索,这样可以在前面搜索到最少的分支。
②:可行性剪枝
由于我们的半径和高度都是整数所以我们的每一层能取得的最小值就是当前的层数,这个是最小状态并且小于下面一层的高度和半径减一即( R[u + 1] - 1 和 H[u + 1] - 1 )。而我们取得搜索每一层时,我们之后的总体积不能大于 n ,所以我们又得到一点优化,r <= sqrt(n - v), h <= (n - v) / r2 。所以我们需要对两种情况取一个min。即:
u <= r <= min(R[u + 1] - 1, (int)sqrt(n - v)) u <= h <= min(H[u + 1] - 1, (n - v) / r / r)
③:最优性剪枝
如果当前的表面积加上剩余层数的最小表面积都要大于答案那么说明这种方案就是不行的,所以我们就需要预处理处最小的面积和最小的体积。即:
int minv[N], mins[N]; // minv代表到达第u层时所需的最小的体积,mins代表到达第u层是所需的最小侧面积,即每次均比上面一层多1 for (int i = 1; i <= m; i ++ ) { minv[i] = minv[i - 1] + i * i * i; mins[i] = mins[i - 1] + 2 * i * i; } mins[m] += m * m; // 由于是侧面积,所以到达第m层时就要把蛋糕圆面上的面积加上。
④:可行性剪枝(这个剪枝比较难,考试时如果事先不知道那么会很难想出来,北大程序考试时仅有4人做出来,难度可想而知,QAQ)
证明:
假设我们当前位于第 k 层,那么我们可以得到
我们通过对S进行放缩可以得到
进一步得到
证毕。
代码:
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <algorithm> 5 #include <cmath> 6 7 using namespace std; 8 9 const int N = 20, INF = 1e9; 10 11 int n, m; 12 int H[N], R[N]; 13 int minv[N], mins[N]; 14 int res = INF; 15 16 void dfs(int u, int s, int v) 17 { 18 if (v + minv[u] > n) return; 19 if (s + mins[u] >= res) return; 20 if (s + 2 * (n - v) / R[u + 1] >= res) return; 21 22 if (u == 0) 23 { 24 if (v == n) res = s; 25 return; 26 } 27 28 for (int r = min(R[u + 1] - 1, (int)sqrt(n - v)); r >= u; r -- ) 29 for (int h = min(H[u + 1] - 1, (n - v) / r / r); h >= u; h -- ) 30 { 31 int t = 0; 32 if (u == m) t = r * r; 33 R[u] = r, H[u] = h; 34 dfs(u - 1, s + 2 * r * h + t, v + r * r * h); 35 } 36 } 37 38 int main() 39 { 40 cin >> n >> m; 41 42 for (int i = 1; i <= m; i ++ ) 43 { 44 minv[i] = minv[i - 1] + i * i * i; 45 mins[i] = mins[i - 1] + 2 * i * i; 46 } 47 mins[m] += m * m; 48 49 R[m + 1] = H[m + 1] = INF; 50 dfs(m, 0, 0); 51 52 if (res == INF) cout << 0 << endl; 53 else cout << res << endl; 54 55 return 0; 56 }