'commit'
This commit is contained in:
47
TangDou/AcWing/JianZhi/165.cpp
Normal file
47
TangDou/AcWing/JianZhi/165.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
const int N = 20;
|
||||
|
||||
int n, m;
|
||||
int w[N];
|
||||
int sum[N];
|
||||
int ans = N;
|
||||
|
||||
void dfs(int u, int len) {
|
||||
// 最优性剪枝
|
||||
if (len >= ans) return;
|
||||
|
||||
if (u == n + 1) { // 走完
|
||||
ans = len; // 更新答案
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
if (sum[i] + w[u] <= m) { // 能放下的情况
|
||||
sum[i] += w[u]; // 放一下试试
|
||||
dfs(u + 1, len); // 下一只小猫,缆车没有增加
|
||||
sum[i] -= w[u]; // 回溯
|
||||
}
|
||||
|
||||
// 新开一辆车
|
||||
sum[len] += w[u];
|
||||
dfs(u + 1, len + 1);
|
||||
sum[len] -= w[u]; // 恢复现场
|
||||
}
|
||||
|
||||
int main() {
|
||||
cin >> n >> m;
|
||||
for (int i = 1; i <= n; i++) cin >> w[i];
|
||||
|
||||
// 优化搜索顺序 21ms
|
||||
// 不加优化搜索顺序 88ms
|
||||
// Q:为什么可以采用这样的优化策略?
|
||||
// A: dfs搜索的顺序很关键,很玄学,本着有矛盾先暴露,将一些分枝尽早结束,提前返回,可以提高搜索效率
|
||||
sort(w + 1, w + 1 + n, greater<int>());
|
||||
|
||||
dfs(1, 0);
|
||||
|
||||
cout << ans << endl;
|
||||
return 0;
|
||||
}
|
||||
93
TangDou/AcWing/JianZhi/165.md
Normal file
93
TangDou/AcWing/JianZhi/165.md
Normal file
@@ -0,0 +1,93 @@
|
||||
## [$AcWing$ $165$ 小猫爬山](https://www.acwing.com/problem/content/167/)
|
||||
|
||||
### 一、题目描述
|
||||
翰翰和达达饲养了 $N$ 只小猫,这天,小猫们要去爬山。
|
||||
|
||||
经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。
|
||||
|
||||
翰翰和达达只好花钱让它们坐索道下山。
|
||||
|
||||
索道上的缆车最大承重量为 $W$,而 $N$ 只小猫的重量分别是 $C_1、C_2……C_N$。
|
||||
|
||||
当然,每辆缆车上的小猫的重量之和不能超过 $W$。
|
||||
|
||||
每租用一辆缆车,翰翰和达达就要付 $1$ 美元,所以他们想知道,**最少** 需要付多少美元才能把这 $N$ 只小猫都运送下山?
|
||||
|
||||
**输入格式**
|
||||
第 $1$ 行:包含两个用空格隔开的整数,$N$ 和 $W$。
|
||||
|
||||
第 $2..N+1$ 行:每行一个整数,其中第 $i+1$ 行的整数表示第 $i$ 只小猫的重量 $C_i$。
|
||||
|
||||
**输出格式**
|
||||
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。
|
||||
|
||||
**数据范围**
|
||||
$1≤N≤18,1≤Ci≤W≤10^8$
|
||||
|
||||
**输入样例**:
|
||||
```cpp {.line-numbers}
|
||||
5 1996
|
||||
1
|
||||
2
|
||||
1994
|
||||
12
|
||||
29
|
||||
```
|
||||
|
||||
**输出样例**:
|
||||
```cpp {.line-numbers}
|
||||
2
|
||||
```
|
||||
|
||||
|
||||
### 二、实现代码
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
|
||||
const int N = 20;
|
||||
|
||||
int n, m;
|
||||
int w[N];
|
||||
int sum[N];
|
||||
int ans = N;
|
||||
|
||||
void dfs(int u, int len) {
|
||||
// 最优性剪枝
|
||||
if (len >= ans) return;
|
||||
|
||||
if (u == n + 1) { //走完
|
||||
ans = len; //更新答案
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
if (sum[i] + w[u] <= m) { //能放下的情况
|
||||
sum[i] += w[u]; //放一下试试
|
||||
dfs(u + 1, len); //下一只小猫,缆车没有增加
|
||||
sum[i] -= w[u]; //回溯
|
||||
}
|
||||
|
||||
// 新开一辆车
|
||||
sum[len] += w[u];
|
||||
dfs(u + 1, len + 1);
|
||||
sum[len] -= w[u]; // 恢复现场
|
||||
}
|
||||
|
||||
int main() {
|
||||
cin >> n >> m;
|
||||
for (int i = 1; i <= n; i++) cin >> w[i];
|
||||
|
||||
// 优化搜索顺序 21ms
|
||||
// 不加优化搜索顺序 88ms
|
||||
// Q:为什么可以采用这样的优化策略?
|
||||
// A: dfs搜索的顺序很关键,很玄学,本着有矛盾先暴露,将一些分枝尽早结束,提前返回,可以提高搜索效率
|
||||
sort(w + 1, w + 1 + n, greater<int>());
|
||||
|
||||
dfs(1, 0);
|
||||
|
||||
cout<< ans <<endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
272
TangDou/AcWing/JianZhi/166.md
Normal file
272
TangDou/AcWing/JianZhi/166.md
Normal file
@@ -0,0 +1,272 @@
|
||||
## [$AcWing$ $166$. 数独](https://www.acwing.com/problem/content/168/)
|
||||
|
||||
### 一、题目描述
|
||||
**数独** 是一种传统益智游戏,你需要把一个 $9×9$ 的数独补充完整,使得数独中每行、每列、每个 $3×3$ 的九宫格内数字 $1$∼$9$ 均恰好出现一次。
|
||||
<center><img src='https://www.acwing.com/media/article/image/2019/01/16/19_8cb8eda618-%E6%95%B0%E7%8B%AC.png'></center>
|
||||
请编写一个程序填写数独。
|
||||
|
||||
**输入格式**
|
||||
输入包含多组测试用例。
|
||||
|
||||
每个测试用例占一行,包含 $81$ 个字符,代表数独的 $81$ 个格内数据(顺序总体由上到下,同行由左到右)。
|
||||
|
||||
每个字符都是一个数字($1−9$)或一个 $.$(表示尚未填充)。
|
||||
|
||||
您可以假设输入中的每个谜题都只有一个解决方案。
|
||||
|
||||
文件结尾处为包含单词 $end$ 的单行,表示输入结束。
|
||||
|
||||
**输出格式**
|
||||
每个测试用例,输出一行数据,代表填充完全后的数独。
|
||||
|
||||
**输入样例:**
|
||||
```cpp {.line-numbers}
|
||||
4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
|
||||
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
|
||||
end
|
||||
```
|
||||
|
||||
**输出样例:**
|
||||
```cpp {.line-numbers}
|
||||
417369825632158947958724316825437169791586432346912758289643571573291684164875293
|
||||
416837529982465371735129468571298643293746185864351297647913852359682714128574936
|
||||
```
|
||||
|
||||
### 二、状态压缩+暴搜
|
||||
首先介绍普通的 **暴搜**,就是 **自左而右,自上而下的去填充数字** ,直到找到最优解为止。
|
||||
|
||||
#### 状态压缩
|
||||
|
||||
要想在某个位置上填充`x`,就需要判断`x`在 **同一行** 、 **同一列** 以及 **同一个九宫格** 是否出现过。
|
||||
|
||||
|
||||
令`r[i]`表示第`i`行的状态,`r[i] = 100010001`表示第`i`行 **数字** `1`,`5`,`9`已经被填充上了,如果想在第`i`行的某个空位上填充`k`,需要判断`r[i] >> k & 1`是否为`0`,只有`r[i]`的第`k`位是`0`才可以填充,当然,还需要用`c[i]`表示第`i`列的状态。
|
||||
> **解释**:`r[i] >> k & 1 ==1`表示数字$k$已在同行被使用过了
|
||||
|
||||
一共有`9`个九宫格,也用`0-8`去编号下,再用`w[i]`表示第`i`个九宫格的状态。只要`k`在 **同一行、同一列、同一个九宫格** 都没有出现过,就可以去填充了。
|
||||
|
||||
这里实现的细节要注意,为了方便,读入时比如第`0`行填充了`3`,我们需要写成`r[0] += 1 << 3`;就算`1-9`全部填满,也只是把二进制的`1-9`位全部置为了`1`,而第`0`位我们不用去管它,也就是说,实际上我们是维护一个宽度为`10`的**二进制数**,然后只去考虑第`1-9`位上的数是否为`1`。
|
||||
|
||||
|
||||
#### $dfs$的过程
|
||||
找到一个**空位**,枚举`1-9`,当这个数 **不曾出现** 在 **同一行、同一列、同一九宫格** 的时候就填充这个数,并且把该行、该列、该九宫格这个数对应的**二进制位置**设置为`1`,然后遍历 **下一个位置**,遍历完后也要 **恢复** 所有 **全局数组的状态**
|
||||
|
||||
另外,由于每个数独的答案可能不唯一,找到其中一个就可以返回,不用继续$dfs$了。如果只有单个输入,找到解后直接`exit(0)`即可,但是本题是多组用例,所以为了让$dfs$尽快返回,让递归栈里的内容快速弹出,这里在找到最优解后设置一个标志变量`flag`的值为`true`,后面$dfs$只要遇见`flag`是`true`的情况直接返回即可:
|
||||
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
const int N = 10;
|
||||
int g[N][N]; // 棋盘
|
||||
// 行,列,九宫格分别用的状态压缩桶
|
||||
// 索引:行,列,九宫格是0~8的索引
|
||||
// 内容值:1~9位,状态压缩,0位没有用,不用管它。
|
||||
int r[N], c[N], w[N];
|
||||
bool flag; // 是不是搜索到了第一个答案,如果是,则让其它搜索快速返回,剪一下枝
|
||||
|
||||
// 返回九宫格的编号
|
||||
int get(int x, int y) {
|
||||
x /= 3, y /= 3; // 注意两个都是/3,表示是第几个小方块,可以理解为0~8,共9个小方块
|
||||
return x * 3 + y; // 通过上面的变换,映射到0~8数字上
|
||||
}
|
||||
|
||||
// x,y这个坐标开始暴搜
|
||||
// cnt:还有多少个点需要填充
|
||||
void dfs(int x, int y, int cnt) {
|
||||
if (flag) return; // 快速返回,剪枝
|
||||
if (cnt == 0) { // 没有空格子了
|
||||
// 输出答案
|
||||
for (int i = 0; i < 9; i++)
|
||||
for (int j = 0; j < 9; j++)
|
||||
cout << g[i][j];
|
||||
cout << endl;
|
||||
// 修改第一次完成的标识
|
||||
flag = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int nx = x, ny = y + 1; // 下一个位置,默认是向右
|
||||
if (ny == 9) nx++, ny = 0; // 越界了就从下一行开始
|
||||
|
||||
if (g[x][y] == 0) { // 如果此位置是空白
|
||||
int u = get(x, y); // 这是第几号九宫格
|
||||
for (int i = 1; i <= 9; i++) { // 尝试填充1~9
|
||||
if (r[x] >> i & 1) continue;
|
||||
if (c[y] >> i & 1) continue;
|
||||
if (w[u] >> i & 1) continue;
|
||||
// 修改状态,修改棋盘
|
||||
r[x] += 1 << i, c[y] += 1 << i, w[u] += 1 << i, g[x][y] = i;
|
||||
// 尝试下一个位置
|
||||
dfs(nx, ny, cnt - 1);
|
||||
// 恢复状态
|
||||
r[x] -= 1 << i, c[y] -= 1 << i, w[u] -= 1 << i, g[x][y] = 0;
|
||||
}
|
||||
} else // 如果此位置不是空白,那么就尝试下一个位置
|
||||
dfs(nx, ny, cnt);
|
||||
}
|
||||
int main() {
|
||||
string str; // 输入的棋盘
|
||||
while (cin >> str && str[0] != 'e') {
|
||||
// 初始化
|
||||
memset(g, 0, sizeof g); // 清空地图
|
||||
memset(r, 0, sizeof r); // 清空行的标识
|
||||
memset(c, 0, sizeof c); // 清空列的标识
|
||||
memset(w, 0, sizeof w); // 清空9个小方块的标识
|
||||
// 找到第一个方案的停止标识
|
||||
flag = false;
|
||||
|
||||
// 记录需要填充的空位数量
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < str.size(); i++) {
|
||||
if (str[i] == '.') {
|
||||
cnt++;
|
||||
continue;
|
||||
}
|
||||
// 转换为行列坐标
|
||||
int x = i / 9, y = i % 9; // x:行,y:列
|
||||
int u = get(x, y); // 0~8哪个九宫格
|
||||
g[x][y] = str[i] - '0'; // 棋盘上放上数字
|
||||
r[x] += 1 << g[x][y]; // 本质上是一个状态压缩,用二进制方式描述 x行上 g[x][y]这个数字的占用情况,1表示已使用,0表示未使用
|
||||
c[y] += 1 << g[x][y]; // 列也用状态压缩描述
|
||||
w[u] += 1 << g[x][y]; // 九宫格也用状态压缩描述
|
||||
}
|
||||
// 从左上角暴搜,cnt描述还有多少个位置需要搜索,当cnt==0时停止
|
||||
dfs(0, 0, cnt);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 四、$dfs$ + 优化搜索顺序
|
||||
上面的代码虽然思路比较简洁,按顺序去$dfs$,在本题中却会 **超时**,当然,如果不是多组测试用例肯定不会超时的,因此,需要进行 **剪枝** 。首先要注意到按顺序$dfs$可能会造成很大的 **冗余**。
|
||||
|
||||
比如说一个数独初始时最后一行除了第一列都已经有数字了,只剩下`2`可以被填充到第一个位置了。如果是我们人去写数独,肯定是先写这种可以填充的数是唯一的位置,然后再去写其他位置。为什么我们会采取这样的 **策略** 呢?一开始我们就知道最后一行第一列只能填充`2`,所以其他行的第一列都不能填`2`,否则就找不到解了。但是方法一的这种搜索必然会导致,枚举第一行第一列时填个`2`,此时没有违背数独的规则,然后沿着搜索树中`2`后面的分支搜索个遍,没找到答案再去改第一行第一列上的数。不止如此,$dfs$在枚举第二行、第三行一直到第八行第一列位置上的数时也都会考虑`2`,这将 **耗费大量** 的时间。如果我们最先枚举的就是最后一行第一列,先把`2`填进去,这些冗余都是可以避免的,所以,**本题搜索顺序很重要**。我们应该优先搜索能填数字少的地方。
|
||||
|
||||
方法一中,在枚举`(x,y)`上能填充的数字时,我们是先看这个数字是否在第`x`行、第`y`列、第`u`个九宫格中出现过,设此时行、列、九宫格的状态分别是`r`、`c`和`w`,也就是说。我们是判断`r`中第`k`位是`0`,`c`中第`k`位是`0`,`w`中第`k`位也是`0`才去填充`k`的。设`st = r | c | w`,三个对应位都是`0`的位置才能够填充,因此只要三种状态或起来的状态`st`的第`k`为是`0`就可以填充了,`st`状态中有几个`0`,就**代表这个位置可以填几种数**,我们需要优先枚举`0`最少也就是`1`最多的状态。换而言之,就是要遍历数独中所有的空位,找到这些位置中`st`状态中`1`最多的位置优先填充。一个二进制数中有多少个`1`,可以先预处理出来。前面说过,这里状态表示实际上是用了`10`位的二进制数,所以将$2^{10} = 1024$种状态中`1`的个数都记录下来即可。统计`1`的个数用`lowbit`运算即可,比较简单这里不再赘述了。
|
||||
|
||||
#### 预备知识
|
||||
利用$lowbit$ 求数字二进制表示中`1`的个数:
|
||||
```cpp {.line-numbers}
|
||||
#define lowbit(x) (x & -x) //通常用于获取二进制中最低位的值,即最右边的1的位置。
|
||||
int count(int x) {
|
||||
int cnt = 0;
|
||||
while (x) x -= lowbit(x), cnt++;
|
||||
return cnt;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 五、优化解法
|
||||
本题的时间卡的很死,如果使用$lowbit$现用现算,仍然会$TLE$, 需要进一步使用**预处理**,将$0 \sim 2^n$数字,提前一次性计算完它当中二进制表示有多少个数位是$1$,这样可以避免重复计算,才能正常$AC$本题。
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int N = 10;
|
||||
const int M = (1 << 10) + 10; // 2^10 个可能出现的数字
|
||||
|
||||
int g[N][N]; // 棋盘
|
||||
// 行,列,九宫格分别用的状态压缩桶
|
||||
// 索引:行,列,九宫格是0~8的索引
|
||||
// 内容值:1~9位,状态压缩,0位没有用,不用管它。
|
||||
int r[N], c[N], w[N];
|
||||
bool flag; // 是不是搜索到了第一个答案,如果是,则让其它搜索快速返回,剪一下枝
|
||||
int one[M]; // 每个0~ 2^10-1 的数字当中,每个数字的二进制表示法中数字1的个数
|
||||
|
||||
// 方法1
|
||||
#define lowbit(x) (x & -x)
|
||||
int count(int x) {
|
||||
int cnt = 0;
|
||||
while (x) x -= lowbit(x), cnt++;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
// 返回九宫格的编号
|
||||
int get(int x, int y) {
|
||||
x /= 3, y /= 3; // 注意两个都是/3,表示是第几个小方块,可以理解为0~8,共9个小方块
|
||||
return x * 3 + y; // 通过上面的变换,映射到0~8数字上
|
||||
}
|
||||
|
||||
void getPos(int &x, int &y) {
|
||||
int res = -1; // 每个位置上,已经占用数字最多的是多少个
|
||||
// 每次通过9*9的双重循环,枚举出每次哪个位置上需要尝试的数字数量是最少的
|
||||
for (int i = 0; i < 9; i++) {
|
||||
for (int j = 0; j < 9; j++) {
|
||||
if (g[i][j]) continue; // 如果已经填充过,就不用理
|
||||
int u = get(i, j); // 第几个九宫格
|
||||
int t = r[i] | c[j] | w[u]; // 三者并集生成的数字
|
||||
if (one[t] > res) { // 这个数字里面的数字1的个数如果大于当前值,修改之。找到1最多的位置
|
||||
res = one[t]; // 更新个数最大值
|
||||
x = i, y = j; // 记录当前的坐标
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cnt:还有多少个点需要填充
|
||||
void dfs(int cnt) {
|
||||
if (flag) return; // 快速返回,剪枝
|
||||
if (cnt == 0) { // 没有空格子了
|
||||
// 输出答案
|
||||
for (int i = 0; i < 9; i++)
|
||||
for (int j = 0; j < 9; j++)
|
||||
cout << g[i][j];
|
||||
cout << endl;
|
||||
// 修改第一次完成的标识
|
||||
flag = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int x, y; // 记录当前找到的可能性最少的那个点,nx,ny:坐标,nu:哪个九宫格
|
||||
|
||||
// 查询获取到最合理的位置作为尝试顺序
|
||||
getPos(x, y);
|
||||
|
||||
// 这是第几个九宫格
|
||||
int u = get(x, y);
|
||||
for (int i = 1; i <= 9; i++) {
|
||||
if (r[x] >> i & 1) continue; // 如果同行出现过,不能填
|
||||
if (c[y] >> i & 1) continue; // 如果同列出现过,不能填
|
||||
if (w[u] >> i & 1) continue; // 如果同九宫格出现过,不能填
|
||||
// 模拟填入
|
||||
r[x] += 1 << i, c[y] += 1 << i, w[u] += 1 << i, g[x][y] = i;
|
||||
// 递归下一个位置
|
||||
dfs(cnt - 1);
|
||||
// 恢复现场
|
||||
r[x] -= 1 << i, c[y] -= 1 << i, w[u] -= 1 << i, g[x][y] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
// 预处理 0~ 2^N 中每个数字二进制表示中数字1的个数
|
||||
for (int i = 0; i < 1 << N; i++) one[i] = count(i);
|
||||
|
||||
string str;
|
||||
while (cin >> str && str[0] != 'e') {
|
||||
// 全部清空一次,因为有多组测试数据
|
||||
memset(g, 0, sizeof g);
|
||||
memset(r, 0, sizeof r);
|
||||
memset(c, 0, sizeof c);
|
||||
memset(w, 0, sizeof w);
|
||||
// 当前测试数据,还没有找到第一个合理解
|
||||
flag = false;
|
||||
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < str.size(); i++) {
|
||||
if (str[i] == '.') {
|
||||
cnt++;
|
||||
continue;
|
||||
}
|
||||
int x = i / 9, y = i % 9;
|
||||
int u = get(x, y);
|
||||
g[x][y] = str[i] - '0';
|
||||
r[x] += 1 << g[x][y];
|
||||
c[y] += 1 << g[x][y];
|
||||
w[u] += 1 << g[x][y];
|
||||
}
|
||||
dfs(cnt);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
83
TangDou/AcWing/JianZhi/166_1.cpp
Normal file
83
TangDou/AcWing/JianZhi/166_1.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
const int N = 10;
|
||||
int g[N][N]; // 棋盘
|
||||
// 行,列,九宫格分别用的状态压缩桶
|
||||
// 索引:行,列,九宫格是0~8的索引
|
||||
// 内容值:1~9位,状态压缩,0位没有用,不用管它。
|
||||
int r[N], c[N], w[N];
|
||||
bool flag; // 是不是搜索到了第一个答案,如果是,则让其它搜索快速返回,剪一下枝
|
||||
|
||||
// 返回九宫格的编号
|
||||
int get(int x, int y) {
|
||||
x /= 3, y /= 3; // 注意两个都是/3,表示是第几个小方块,可以理解为0~8,共9个小方块
|
||||
return x * 3 + y; // 通过上面的变换,映射到0~8数字上
|
||||
}
|
||||
|
||||
// x,y这个坐标开始暴搜
|
||||
// cnt:还有多少个点需要填充
|
||||
void dfs(int x, int y, int cnt) {
|
||||
if (flag) return; // 快速返回,剪枝
|
||||
if (cnt == 0) { // 没有空格子了
|
||||
// 输出答案
|
||||
for (int i = 0; i < 9; i++)
|
||||
for (int j = 0; j < 9; j++)
|
||||
cout << g[i][j];
|
||||
cout << endl;
|
||||
// 修改第一次完成的标识
|
||||
flag = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int nx = x, ny = y + 1; // 下一个位置,默认是向右
|
||||
if (ny == 9) nx++, ny = 0; // 越界了就从下一行开始
|
||||
|
||||
if (g[x][y] == 0) { // 如果此位置是空白
|
||||
int u = get(x, y); // 这是第几号九宫格
|
||||
for (int i = 1; i <= 9; i++) { // 尝试填充1~9
|
||||
if (r[x] >> i & 1) continue;
|
||||
if (c[y] >> i & 1) continue;
|
||||
if (w[u] >> i & 1) continue;
|
||||
// 修改状态,修改棋盘
|
||||
r[x] += 1 << i, c[y] += 1 << i, w[u] += 1 << i, g[x][y] = i;
|
||||
// 尝试下一个位置
|
||||
dfs(nx, ny, cnt - 1);
|
||||
// 恢复状态
|
||||
r[x] -= 1 << i, c[y] -= 1 << i, w[u] -= 1 << i, g[x][y] = 0;
|
||||
}
|
||||
} else // 如果此位置不是空白,那么就尝试下一个位置
|
||||
dfs(nx, ny, cnt);
|
||||
}
|
||||
int main() {
|
||||
string str; // 输入的棋盘
|
||||
while (cin >> str && str[0] != 'e') {
|
||||
// 初始化
|
||||
memset(g, 0, sizeof g); // 清空地图
|
||||
memset(r, 0, sizeof r); // 清空行的标识
|
||||
memset(c, 0, sizeof c); // 清空列的标识
|
||||
memset(w, 0, sizeof w); // 清空9个小方块的标识
|
||||
// 找到第一个方案的停止标识
|
||||
flag = false;
|
||||
|
||||
// 记录需要填充的空位数量
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < str.size(); i++) {
|
||||
if (str[i] == '.') {
|
||||
cnt++;
|
||||
continue;
|
||||
}
|
||||
// 转换为行列坐标
|
||||
int x = i / 9, y = i % 9; // x:行,y:列
|
||||
int u = get(x, y); // 0~8哪个九宫格
|
||||
g[x][y] = str[i] - '0'; // 棋盘上放上数字
|
||||
r[x] += 1 << g[x][y]; // 本质上是一个状态压缩,用二进制方式描述 x行上 g[x][y]这个数字的占用情况,1表示已使用,0表示未使用
|
||||
c[y] += 1 << g[x][y]; // 列也用状态压缩描述
|
||||
w[u] += 1 << g[x][y]; // 九宫格也用状态压缩描述
|
||||
}
|
||||
// 从左上角暴搜,cnt描述还有多少个位置需要搜索,当cnt==0时停止
|
||||
dfs(0, 0, cnt);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
108
TangDou/AcWing/JianZhi/166_2.cpp
Normal file
108
TangDou/AcWing/JianZhi/166_2.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int N = 10;
|
||||
const int M = (1 << 10) + 10; // 2^10 个可能出现的数字
|
||||
|
||||
int g[N][N]; // 棋盘
|
||||
// 行,列,九宫格分别用的状态压缩桶
|
||||
// 索引:行,列,九宫格是0~8的索引
|
||||
// 内容值:1~9位,状态压缩,0位没有用,不用管它。
|
||||
int r[N], c[N], w[N];
|
||||
bool flag; // 是不是搜索到了第一个答案,如果是,则让其它搜索快速返回,剪一下枝
|
||||
int one[M]; // 每个0~ 2^10-1 的数字当中,每个数字的二进制表示法中数字1的个数
|
||||
|
||||
// 方法1
|
||||
#define lowbit(x) (x & -x)
|
||||
int count(int x) {
|
||||
int cnt = 0;
|
||||
while (x) x -= lowbit(x), cnt++;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
// 返回九宫格的编号
|
||||
int get(int x, int y) {
|
||||
x /= 3, y /= 3; // 注意两个都是/3,表示是第几个小方块,可以理解为0~8,共9个小方块
|
||||
return x * 3 + y; // 通过上面的变换,映射到0~8数字上
|
||||
}
|
||||
|
||||
void getPos(int &x, int &y) {
|
||||
int res = -1; // 每个位置上,已经占用数字最多的是多少个
|
||||
// 每次通过9*9的双重循环,枚举出每次哪个位置上需要尝试的数字数量是最少的
|
||||
for (int i = 0; i < 9; i++) {
|
||||
for (int j = 0; j < 9; j++) {
|
||||
if (g[i][j]) continue; // 如果已经填充过,就不用理
|
||||
int u = get(i, j); // 第几个九宫格
|
||||
int t = r[i] | c[j] | w[u]; // 三者并集生成的数字
|
||||
if (one[t] > res) { // 这个数字里面的数字1的个数如果大于当前值,修改之。找到1最多的位置
|
||||
res = one[t]; // 更新个数最大值
|
||||
x = i, y = j; // 记录当前的坐标
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cnt:还有多少个点需要填充
|
||||
void dfs(int cnt) {
|
||||
if (flag) return; // 快速返回,剪枝
|
||||
if (cnt == 0) { // 没有空格子了
|
||||
// 输出答案
|
||||
for (int i = 0; i < 9; i++)
|
||||
for (int j = 0; j < 9; j++)
|
||||
cout << g[i][j];
|
||||
cout << endl;
|
||||
// 修改第一次完成的标识
|
||||
flag = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int x, y; // 记录当前找到的可能性最少的那个点,nx,ny:坐标,nu:哪个九宫格
|
||||
|
||||
// 查询获取到最合理的位置作为尝试顺序
|
||||
getPos(x, y);
|
||||
|
||||
// 这是第几个九宫格
|
||||
int u = get(x, y);
|
||||
for (int i = 1; i <= 9; i++) {
|
||||
if (r[x] >> i & 1) continue; // 如果同行出现过,不能填
|
||||
if (c[y] >> i & 1) continue; // 如果同列出现过,不能填
|
||||
if (w[u] >> i & 1) continue; // 如果同九宫格出现过,不能填
|
||||
// 模拟填入
|
||||
r[x] += 1 << i, c[y] += 1 << i, w[u] += 1 << i, g[x][y] = i;
|
||||
// 递归下一个位置
|
||||
dfs(cnt - 1);
|
||||
// 恢复现场
|
||||
r[x] -= 1 << i, c[y] -= 1 << i, w[u] -= 1 << i, g[x][y] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
// 预处理 0~ 2^N 中每个数字二进制表示中数字1的个数
|
||||
for (int i = 0; i < 1 << N; i++) one[i] = count(i);
|
||||
|
||||
string str;
|
||||
while (cin >> str && str[0] != 'e') {
|
||||
// 全部清空一次,因为有多组测试数据
|
||||
memset(g, 0, sizeof g);
|
||||
memset(r, 0, sizeof r);
|
||||
memset(c, 0, sizeof c);
|
||||
memset(w, 0, sizeof w);
|
||||
// 当前测试数据,还没有找到第一个合理解
|
||||
flag = false;
|
||||
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < str.size(); i++) {
|
||||
if (str[i] == '.') {
|
||||
cnt++;
|
||||
continue;
|
||||
}
|
||||
int x = i / 9, y = i % 9;
|
||||
int u = get(x, y);
|
||||
g[x][y] = str[i] - '0';
|
||||
r[x] += 1 << g[x][y];
|
||||
c[y] += 1 << g[x][y];
|
||||
w[u] += 1 << g[x][y];
|
||||
}
|
||||
dfs(cnt);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
310
TangDou/AcWing/JianZhi/167.md
Normal file
310
TangDou/AcWing/JianZhi/167.md
Normal file
@@ -0,0 +1,310 @@
|
||||
## [$AcWing$ $167$. 木棒](https://www.acwing.com/problem/content/169/)
|
||||
|
||||
### 一、题目描述
|
||||
乔治拿来 **一组等长** 的木棒,将它们随机地砍断,使得每一节木棍的长度都 **不超过** $50$ 个长度单位。
|
||||
|
||||
然后他又想把这些木棍 **恢复到为裁截前的状态** ,但忘记了 **初始时有多少木棒** 以及 **木棒的初始长度**。
|
||||
|
||||
请你设计一个程序,帮助乔治计算木棒的 **可能最小长度**。
|
||||
|
||||
每一节木棍的长度都用大于零的整数表示。
|
||||
|
||||
**输入格式**
|
||||
输入包含多组数据,每组数据包括两行:
|
||||
|
||||
第一行是一个不超过 $64$ 的整数,表示砍断之后共有多少节木棍。
|
||||
|
||||
第二行是截断以后,所得到的各节木棍的长度。
|
||||
|
||||
在最后一组数据之后,是一个零。
|
||||
|
||||
**输出格式**
|
||||
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。
|
||||
|
||||
|
||||
**备注**
|
||||
> $1≤N≤60$
|
||||
|
||||
**输入样例**:
|
||||
```cpp {.line-numbers}
|
||||
9
|
||||
5 2 1 5 2 1 5 2 1
|
||||
4
|
||||
1 2 3 4
|
||||
0
|
||||
```
|
||||
|
||||
**输出样例**:
|
||||
```cpp {.line-numbers}
|
||||
6
|
||||
5
|
||||
```
|
||||
|
||||
### 二、朴素$dfs$
|
||||
<font color='red' size=4><b>这道题可以说是搜索剪枝例题中的经典,如果能够完全吃透这道题,对剪枝的领悟一定能更深!</b></font>
|
||||
|
||||
<!-- 让表格居中显示的风格 -->
|
||||
<style>
|
||||
.center
|
||||
{
|
||||
width: auto;
|
||||
display: table;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
<div class="center">
|
||||
|
||||
| 东西 | 尺寸 |
|
||||
| ---- | ---- |
|
||||
| 木棒 | 大 |
|
||||
| 木棍 | 小 |
|
||||
</div>
|
||||
|
||||
**朴素版本** 的搜索代码:
|
||||
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
const int N = 65;
|
||||
int a[N], n, len, sum;
|
||||
bool st[N];
|
||||
|
||||
// len:每个木棒的长度
|
||||
// u:当前正在装的木棒
|
||||
// last:当前木棒已经装入的木棍长度
|
||||
bool dfs(int u, int last) {
|
||||
// 如果上一个木棒填充完整,开启了新的木棒,那么,新木棒的当前木棍总长是0,同时,如果u个木棒全部装满就可以包含所有,就是找到了合适的木棒长度和木棒个数
|
||||
if (u * len == sum) return true;
|
||||
|
||||
// 如果当前木棒已填满,开启下一个木棒
|
||||
if (last == len) return dfs(u + 1, 0);
|
||||
|
||||
for (int i = 0; i < n; i++) { // 枚举每一个木棍
|
||||
if (st[i] || last + a[i] > len) continue; // 如果使过了,或者,当前木棒剩余空间不足以装入i木棍,放过
|
||||
|
||||
st[i] = true; // 装入
|
||||
if (dfs(u, last + a[i])) return true; // 装入后,此路径的后续汇报了成功标识,则本路径就是成功的,不需要再继续搜索了
|
||||
// 注意:这里不能直接 return dfs(u,last+a[i]),原因是因为即使按现在这样的装法不能成功,但不代表着不可以尝试其它的木棍,不能直接 return
|
||||
// 这个bool和void的使用方法有一些区别,冉要仔细体会 ,也可以理解为在使用 bool dfs时,return要小心再小心,就能不犯错误
|
||||
st[i] = false; // 回溯
|
||||
}
|
||||
return false; // 如果上面的代码中未能return true,就表示进入了一条死的路径,当前len不合法 或者是 当前木棒选择的木棍不正确
|
||||
}
|
||||
int main() {
|
||||
while (cin >> n && n) {
|
||||
sum = 0;
|
||||
memset(st, false, sizeof st);
|
||||
for (int i = 0; i < n; i++) cin >> a[i], sum += a[i];
|
||||
// 从1~sum,由小到大逐个枚举木棒长度,这样,可以保证第一个符合条件的木棒长度是最小的
|
||||
// 在 木棒长度=len的前提下,开始向第1个木棒里装东西,目前第1个木棒内已装入的长度为0
|
||||
// 如果当前木棒长度=len的情况下,可以找到完美的装填办法,就是找到的答案
|
||||
for (len = 1; len <= sum; len++) {
|
||||
if (dfs(1, 0)) {
|
||||
printf("%d\n", len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
毫无疑问,$TLE$了,需要优化。
|
||||
|
||||
### 三、优化
|
||||
#### 1、木棒长度一定是所有木棍总长度的约数
|
||||
因为原来每根木棒长度是相同的,所以
|
||||
$$\large sum(总长度)=len(单个木棒长度)*num(木棒数量)$$
|
||||
也就是一定要有$$\large sum\% len=0$$
|
||||
|
||||
> 也就是说我们在枚举$len$时,可以通过上面的性质,避免一些无效的、肯定不对的木棒长度枚举,从而起到优化的目的。
|
||||
|
||||
#### 2、要组合不要排列
|
||||
这个问题中的木棒长度跟搭配 **顺序** **没有关系** ,比如编号为$[1,2,3]$结合的木棍,与编号为$[3,2,1]$结合的木棍,视为同一个组合,我们只要控制好枚举的顺序,就可以得到唯一的组合搭配顺序,而不需要枚举出所有的排列形式。这一点也很好实现:每个木棒开始时,从下标$0$开始找木棍进行填充,并且记录这个上一个是从哪个下标开始的,新选择的木棍号码,一定要比上一个大就行,不可以选择比上一个小的,防止了非单调递增序的产生,下面的我们首先需要理解的组合与排列的对比代码:
|
||||
|
||||
**排列**
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
const int N = 20;
|
||||
int n, m;
|
||||
bool st[N];
|
||||
vector<int> path;
|
||||
|
||||
void dfs(int u) {
|
||||
if (u == m) {
|
||||
for (int i = 0; i < path.size(); i++)
|
||||
cout << path[i] << " ";
|
||||
cout << endl;
|
||||
return;
|
||||
}
|
||||
for (int i = 1; i <= n; i++) {
|
||||
if (!st[i]) {
|
||||
path.push_back(i);
|
||||
st[i] = true;
|
||||
dfs(u + 1);
|
||||
st[i] = false;
|
||||
path.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 测试用例:
|
||||
// 5 3
|
||||
int main() {
|
||||
cin >> n >> m;
|
||||
dfs(0);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**组合**
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
const int N = 20;
|
||||
int n, m;
|
||||
bool st[N];
|
||||
vector<int> path;
|
||||
|
||||
void dfs(int u, int start) {
|
||||
if (u == m) {
|
||||
for (int i = 0; i < path.size(); i++) cout << path[i] << " ";
|
||||
cout << endl;
|
||||
return;
|
||||
}
|
||||
for (int i = start; i <= n; i++) {
|
||||
if (!st[i]) {
|
||||
path.push_back(i);
|
||||
st[i] = true;
|
||||
dfs(u + 1, i + 1);
|
||||
st[i] = false;
|
||||
path.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 测试用例:
|
||||
// 5 3
|
||||
int main() {
|
||||
cin >> n >> m;
|
||||
dfs(0, 1);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3、优化搜索顺序
|
||||
有了运输小猫那道题的经验,我们知道:将木棍按 **由大到小** 排序,然后去深搜,可以让搜索更快。
|
||||
|
||||
**原理**:先枚举大的,后面的搜索空间就少了,可以减少分支。
|
||||
|
||||
#### 4、当填充某个木棍失败时(回溯后)
|
||||
|
||||
* $①$ 在一个木棒结尾正好装下,但后续动作无法完成完美填充
|
||||
* $②$ 在一个空木棒头部放木棍,但后续动作无法完成完美填充
|
||||
|
||||
|
||||
**推论**
|
||||
假如一根小木棍放在当前大木棒中,正好在尾部填充满,无法完成后续的完美填充,那么可以判断它放在任何一根大木棒中都无法实现完美填充。
|
||||
|
||||
**证明**
|
||||
反证法:假如有一根小木棍放在当前大木棒$x$的最后一根不行,但是放在其他大木$y$的某个位置行,则大木$y$中该小木棍可以通过平移使得该小木棍置于 **最后一个位置**,这样就存在方案使得该小木放在大木棒最后一个位置可行,与假设相悖,得证。
|
||||
|
||||
同理,$②$也是正确的结论。
|
||||
|
||||
### 四、代码实现
|
||||
$23ms$
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int N = 70;
|
||||
int a[N]; // 用来装每个木棍的长度
|
||||
int n; // 木棍个数
|
||||
int len; // 组长度
|
||||
int sum; // 总长度
|
||||
bool st[N]; // 标识某个木棍是不是使用过了
|
||||
|
||||
/*
|
||||
u :木棒号,下标从0开始
|
||||
last:最后一个木棒目前的长度
|
||||
start:从哪个索引号开始继续查找
|
||||
|
||||
只要搜索到解就停止的dfs问题,一般用bool类型作为返回值,因为这样,搜到最优解就返回true,用dfs的返回值作为条件,
|
||||
就可以瞬间退出递归了。比上一题中专门设置标志变量的方法要更好。
|
||||
*/
|
||||
bool dfs(int u, int last, int start) {
|
||||
// 因为填充的逻辑是填充满了一个,才能走到下一个面前,所以如果成功到达了第u个前面的话,说明前u-1个都是填充满的
|
||||
// 如果在第u个面前,检查到木棒长度 乘以 木棒数量 等于总长度,说明完成了所有填充工作,递归终止
|
||||
if (len * u == sum) return true;
|
||||
|
||||
// 因为每一个木棒原来都是客观存在的,所以,每组木棍必须可以填充满一个木棒
|
||||
// 不能填充满,就不能继续填充下一个木棒
|
||||
if (last == len) return dfs(u + 1, 0, 0); // 注意当一组完成时,下一组从0开始搜
|
||||
|
||||
// 在当前木棒没有填充满的情况下,需要继续找某个木棍进行填充
|
||||
// start表示搜索开始的位置
|
||||
// 防止出现 abc ,cba这样的情况,我们要的是组合,不要排列,每次查找选择元素后面的就可以了
|
||||
for (int i = start; i < n; i++) {
|
||||
// 使用过 or 超过枚举的木棒长度
|
||||
if (st[i] || last + a[i] > len) continue;
|
||||
|
||||
// 准备将i号木棍,放入u这个木棒中
|
||||
st[i] = true; // 标识i号木棍已使用过
|
||||
if (dfs(u, last + a[i], start + 1)) return true; // 将i号木棍放入u号木棒中
|
||||
st[i] = false; // 恢复现场
|
||||
|
||||
// 可行性剪枝
|
||||
// 优化4:如果在第u组放置失败,且此时第u组长度为0,这是最理想的状态,这种情况都放不下,那么占用了一些空间的情况下,就肯定更放不下!
|
||||
if (last == 0) return false;
|
||||
|
||||
// 优化5:如果加入一个元素后某个分组和等于len了,但是后续搜索失败了(后续肯定是开新组并且last从0开始),则没有可行解,和4是等价的。
|
||||
if (last + a[i] == len) return false;
|
||||
|
||||
// 优化6:冗余性剪枝
|
||||
// 如果当前未放置成功,则后面和该木棒长度相等的也一样,直接略过即可
|
||||
while (i < n - 1 && a[i] == a[i + 1]) i++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int main() {
|
||||
// 多组测试数据,以0结束输入
|
||||
while (scanf("%d", &n) && n) {
|
||||
// 多组数组初始化
|
||||
sum = 0; // 木棒总长清零
|
||||
memset(st, false, sizeof st); // 清空使用状态桶数组
|
||||
|
||||
// 录入n个木棍的长度
|
||||
for (int i = 0; i < n; i++) scanf("%d", &a[i]), sum += a[i]; // 总长度
|
||||
|
||||
// 优化1:按小猫思路,由大到小排序,因为大卡车越往前越好找空放,越往后越不容易找空
|
||||
// 根据搜索顺序优化,枚举长度小的分支比长度大的分支要多,所以先枚举长度大的
|
||||
sort(a, a + n, greater<int>());
|
||||
|
||||
// 枚举每一个可能的木棒长度,注意这是一个全局量,因为需要控制递归的出口
|
||||
for (len = a[0]; len <= sum; len++) { // 优化2:从最短的木棍长度开始,可以减枝
|
||||
// 优化3:如果总长度不能整除木棒长度,那么不能按这个长度设置木棒长度
|
||||
if (sum % len) continue;
|
||||
|
||||
// 在当前len长度的基础上,开始搜索
|
||||
if (dfs(1, 0, 0)) {
|
||||
printf("%d\n", len); // 找到最短的木棒长度
|
||||
break; // 找到一个就退出
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
### 五、理解与反思
|
||||
> **问题**:老师的做法是把当前组放满,刚好放满了就开新组,如果这个组怎么也放不满,那就说明前面放错了。
|
||||
这个方法和小猫爬山不一样,小猫爬山是对于当前猫,遍历所有的组,看看哪个组能放进去,然后也要尝试开新组,这里并不能贪心说如果有一个组能放就不开新组了。
|
||||
我一直有个困惑,不知道应该是遍历物品(老师的木棍做法)还是遍历组(老师的小猫爬山做法)。
|
||||
|
||||
**答**:
|
||||
小猫爬山,没有说一定要把每台车都装满,如果有空座,但小猫坐不下,太沉了,可以再租一辆车。所以需要枚举小猫,为小猫找车。
|
||||
本题是要求木棍一定要填充满木棒,不填满不算完美解决,所以我们保证必须$A$木棒满了,才能创建$B$木棒
|
||||
46
TangDou/AcWing/JianZhi/167_0.cpp
Normal file
46
TangDou/AcWing/JianZhi/167_0.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include <bits/stdc++.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
const int N = 65;
|
||||
int a[N], n, len, sum;
|
||||
bool st[N];
|
||||
|
||||
// len:每个木棒的长度
|
||||
// u:当前正在装的木棒
|
||||
// last:当前木棒已经装入的木棍长度
|
||||
bool dfs(int u, int last) {
|
||||
// 如果上一个木棒填充完整,开启了新的木棒,那么,新木棒的当前木棍总长是0,同时,如果u个木棒全部装满就可以包含所有,就是找到了合适的木棒长度和木棒个数
|
||||
if (u * len == sum) return true;
|
||||
|
||||
// 如果当前木棒已填满,开启下一个木棒
|
||||
if (last == len) return dfs(u + 1, 0);
|
||||
|
||||
for (int i = 0; i < n; i++) { // 枚举每一个木棍
|
||||
if (st[i] || last + a[i] > len) continue; // 如果使过了,或者,当前木棒剩余空间不足以装入i木棍,放过
|
||||
|
||||
st[i] = true; // 装入
|
||||
if (dfs(u, last + a[i])) return true; // 装入后,此路径的后续汇报了成功标识,则本路径就是成功的,不需要再继续搜索了
|
||||
// 注意:这里不能直接 return dfs(u,last+a[i]),原因是因为即使按现在这样的装法不能成功,但不代表着不可以尝试其它的木棍,不能直接 return
|
||||
// 这个bool和void的使用方法有一些区别,冉要仔细体会 ,也可以理解为在使用 bool dfs时,return要小心再小心,就能不犯错误
|
||||
st[i] = false; // 回溯
|
||||
}
|
||||
return false; // 如果上面的代码中未能return true,就表示进入了一条死的路径,当前len不合法 或者是 当前木棒选择的木棍不正确
|
||||
}
|
||||
int main() {
|
||||
while (cin >> n && n) {
|
||||
sum = 0;
|
||||
memset(st, false, sizeof st);
|
||||
for (int i = 0; i < n; i++) cin >> a[i], sum += a[i];
|
||||
// 从1~sum,由小到大逐个枚举木棒长度,这样,可以保证第一个符合条件的木棒长度是最小的
|
||||
// 在 木棒长度=len的前提下,开始向第1个木棒里装东西,目前第1个木棒内已装入的长度为0
|
||||
// 如果当前木棒长度=len的情况下,可以找到完美的装填办法,就是找到的答案
|
||||
for (len = 1; len <= sum; len++) {
|
||||
if (dfs(1, 0)) {
|
||||
printf("%d\n", len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
83
TangDou/AcWing/JianZhi/167_1.cpp
Normal file
83
TangDou/AcWing/JianZhi/167_1.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
// #include <bits/stdc++.h>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
using namespace std;
|
||||
const int N = 70;
|
||||
int a[N]; // 用来装每个木棍的长度
|
||||
int n; // 木棍个数
|
||||
int len; // 组长度
|
||||
int sum; // 总长度
|
||||
bool st[N]; // 标识某个木棍是不是使用过了
|
||||
|
||||
/*
|
||||
u :木棒号,下标从0开始
|
||||
last:最后一个木棒目前的长度
|
||||
start:从哪个索引号开始继续查找
|
||||
|
||||
只要搜索到解就停止的dfs问题,一般用bool类型作为返回值,因为这样,搜到最优解就返回true,用dfs的返回值作为条件,
|
||||
就可以瞬间退出递归了。比上一题中专门设置标志变量的方法要更好。
|
||||
*/
|
||||
bool dfs(int u, int last, int start) {
|
||||
// 因为填充的逻辑是填充满了一个,才能走到下一个面前,所以如果成功到达了第u个前面的话,说明前u-1个都是填充满的
|
||||
// 如果在第u个面前,检查到木棒长度 乘以 木棒数量 等于总长度,说明完成了所有填充工作,递归终止
|
||||
if (len * u == sum) return true;
|
||||
|
||||
// 因为每一个木棒原来都是客观存在的,所以,每组木棍必须可以填充满一个木棒
|
||||
// 不能填充满,就不能继续填充下一个木棒
|
||||
if (last == len) return dfs(u + 1, 0, 0); // 注意当一组完成时,下一组从0开始搜
|
||||
|
||||
// 在当前木棒没有填充满的情况下,需要继续找某个木棍进行填充
|
||||
// start表示搜索开始的位置
|
||||
// 防止出现 abc ,cba这样的情况,我们要的是组合,不要排列,每次查找选择元素后面的就可以了
|
||||
for (int i = start; i < n; i++) {
|
||||
// 使用过 or 超过枚举的木棒长度
|
||||
if (st[i] || last + a[i] > len) continue;
|
||||
|
||||
// 准备将i号木棍,放入u这个木棒中
|
||||
st[i] = true; // 标识i号木棍已使用过
|
||||
if (dfs(u, last + a[i], start + 1)) return true; // 将i号木棍放入u号木棒中
|
||||
st[i] = false; // 恢复现场
|
||||
|
||||
// 可行性剪枝
|
||||
// 优化4:如果在第u组放置失败,且此时第u组长度为0,这是最理想的状态,这种情况都放不下,那么占用了一些空间的情况下,就肯定更放不下!
|
||||
if (last == 0) return false;
|
||||
|
||||
// 优化5:如果加入一个元素后某个分组和等于len了,但是后续搜索失败了(后续肯定是开新组并且last从0开始),则没有可行解,和4是等价的。
|
||||
if (last + a[i] == len) return false;
|
||||
|
||||
// 优化6:冗余性剪枝
|
||||
// 如果当前未放置成功,则后面和该木棒长度相等的也一样,直接略过即可
|
||||
while (i < n - 1 && a[i] == a[i + 1]) i++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int main() {
|
||||
// 多组测试数据,以0结束输入
|
||||
while (scanf("%d", &n) && n) {
|
||||
// 多组数组初始化
|
||||
sum = 0; // 木棒总长清零
|
||||
memset(st, false, sizeof st); // 清空使用状态桶数组
|
||||
|
||||
// 录入n个木棍的长度
|
||||
for (int i = 0; i < n; i++) scanf("%d", &a[i]), sum += a[i]; // 总长度
|
||||
|
||||
// 优化1:按小猫思路,由大到小排序,因为大卡车越往前越好找空放,越往后越不容易找空
|
||||
// 根据搜索顺序优化,枚举长度小的分支比长度大的分支要多,所以先枚举长度大的
|
||||
sort(a, a + n, greater<int>());
|
||||
|
||||
// 枚举每一个可能的木棒长度,注意这是一个全局量,因为需要控制递归的出口
|
||||
for (len = a[0]; len <= sum; len++) { // 优化2:从最短的木棍长度开始,可以减枝
|
||||
// 优化3:如果总长度不能整除木棒长度,那么不能按这个长度设置木棒长度
|
||||
if (sum % len) continue;
|
||||
|
||||
// 在当前len长度的基础上,开始搜索
|
||||
if (dfs(1, 0, 0)) {
|
||||
printf("%d\n", len); // 找到最短的木棒长度
|
||||
break; // 找到一个就退出
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
67
TangDou/AcWing/JianZhi/168.cpp
Normal file
67
TangDou/AcWing/JianZhi/168.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int INF = 0x3f3f3f3f;
|
||||
const int N = 22;
|
||||
int n; // 待制作的蛋糕的体积为
|
||||
int m; // 蛋糕的层数为 m
|
||||
int ans = INF; // 最小表面积,预求最小,先设最大
|
||||
int mins[N]; // 每一层的最小表面积
|
||||
int minv[N]; // 每一层的最小体积
|
||||
int R[N], H[N]; // 为每一层蛋糕分配的半径和高度
|
||||
|
||||
// u:正在研究第几层
|
||||
// v:已经使用了的体积
|
||||
// s:已经使用了的表面积
|
||||
void dfs(int u, int v, int s) {
|
||||
// 如果来到第u层,发现,已经使用过的体积v,加上第1~u层最小体积minv[u] ,大于整体的体积n,就不用再继续了
|
||||
if (v + minv[u] > n) return;
|
||||
|
||||
// 如果来到第u层,发现,已经使用过的表面各s,加上第u层及以上所有层需要的表面积mins[u]
|
||||
// 大于当前最优解了,就不用再继续研究了,还不如最优解,再研究也是白扯
|
||||
if (s + mins[u] >= ans) return;
|
||||
|
||||
// 第二个最优性剪枝,侧面积和体积不是孤立存在的,是有关联关系的。
|
||||
// 不符合这个关联关系的Ri和Hi是没有必要继续的
|
||||
if (2 * (n - v) / R[u + 1] + s >= ans) return;
|
||||
|
||||
// 摆完所有层蛋糕
|
||||
if (u == 0) {
|
||||
// 如果已经使用的体积等于n,如果是,那么更新答案
|
||||
if (v == n) ans = s;
|
||||
return;
|
||||
}
|
||||
// 枚举每一个可能的半径,同理,半径由大到小
|
||||
for (int r = min(R[u + 1] - 1, (int)sqrt((n - v) / u)); r >= u; r--)
|
||||
for (int h = min(H[u + 1] - 1, (n - v) / r / r); h >= u; h--) {
|
||||
// 如果是第m层,需要加上一个底面积,其它层就只计算侧面积
|
||||
int t = (u == m ? r * r : 0);
|
||||
// 记录下第u层的选择,半径:r,高度:h
|
||||
R[u] = r, H[u] = h; // 因为静态数组可以重复按索引写入,所以不需要回溯
|
||||
// 递归上一层,体积变为v+r*r*h,表面积由s变大为 s + 2 * r * h + t
|
||||
dfs(u - 1, v + r * r * h, s + 2 * r * h + t);
|
||||
}
|
||||
}
|
||||
int main() {
|
||||
cin >> n >> m;
|
||||
// 预处理
|
||||
for (int i = 1; i <= m; i++) {
|
||||
// 第i层,半径最小是i,高度最小是i,否则更小层无法获得整数解
|
||||
// 预处理出每层及上边所有层的最小表面积,看清楚这是一个叠加的关系,不是简单的计算,是递推式
|
||||
mins[i] = mins[i - 1] + 2 * i * i;
|
||||
// 预处理出每层及上边所有层的最小体积
|
||||
minv[i] = minv[i - 1] + i * i * i;
|
||||
}
|
||||
// 初始化为无穷大,防止在计算下层半径高度时取到0
|
||||
R[m + 1] = H[m + 1] = INF;
|
||||
|
||||
// 从下向上进行深搜,按经验来看,这样能减少出发时的分枝数量,效率提升
|
||||
// 从m层出发,此时的体积为0,此时的表面积为0
|
||||
dfs(m, 0, 0);
|
||||
|
||||
// 输出答案
|
||||
if (ans == INF)
|
||||
puts("0");
|
||||
else
|
||||
printf("%d\n", ans);
|
||||
return 0;
|
||||
}
|
||||
230
TangDou/AcWing/JianZhi/168.md
Normal file
230
TangDou/AcWing/JianZhi/168.md
Normal file
@@ -0,0 +1,230 @@
|
||||
## [$AcWing$ $168$. 生日蛋糕](https://www.acwing.com/problem/content/170/)
|
||||
|
||||
### 一、题目描述
|
||||
$7$月$17$日是 $Mr.W$ 的生日,$ACM-THU$ 为此要制作一个体积为 $Nπ$ 的 $M$ 层生日蛋糕,每层都是一个圆柱体。
|
||||
|
||||
设 **从下往上数** 第 $i$ 层蛋糕是半径为 $R_i$,高度为 $H_i$ 的圆柱。
|
||||
|
||||
当 $i<M$ 时,要求 $R_i>R_{i+1}$ 且 $H_i>H_{i+1}$。
|
||||
|
||||
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积 $Q$ 最小。
|
||||
|
||||
令 $Q=Sπ$ ,请编程对给出的 $N$ 和 $M$,找出蛋糕的制作方案(适当的 $R_i$ 和 $H_i$ 的值),使 $S$ 最小。
|
||||
|
||||
除 $Q$ 外,以上所有数据皆为 **正整数**。
|
||||
|
||||
<center><img src='https://imgconvert.csdnimg.cn/aHR0cHM6Ly9jZG4ubHVvZ3Uub3JnL3VwbG9hZC9waWMvNTI0LnBuZw?x-oss-process=image/format,png'></center>
|
||||
|
||||
**输入格式**
|
||||
输入包含两行,第一行为整数 $N$,表示待制作的蛋糕的体积为 $Nπ$。
|
||||
|
||||
第二行为整数 $M$,表示蛋糕的层数为 $M$。
|
||||
|
||||
**输出格式**
|
||||
输出仅一行,是一个正整数 $S$(若无解则 $S=0$)。
|
||||
|
||||
**数据范围**
|
||||
$1≤N≤10000,1≤M≤20$
|
||||
|
||||
**输入样例**:
|
||||
```cpp {.line-numbers}
|
||||
100
|
||||
2
|
||||
```
|
||||
|
||||
**输出样例**:
|
||||
```cpp {.line-numbers}
|
||||
68
|
||||
```
|
||||
|
||||
### 二、题目分析
|
||||
|
||||

|
||||
|
||||
#### 1、梳理题意
|
||||
>将 **自上而下** 的各层蛋糕编号为$1 \sim m$,题目要求我们设计一个 $m$ 层蛋糕,**下层** 的蛋糕比 **上层** 的蛋糕 **半径** 和 **高度** 都 **要大** ,并且总的体积为$n$,求 **最小的表面积**。
|
||||
|
||||
#### 2、公式推导
|
||||
##### 体积
|
||||
$$\large V_{全}=\sum_{i=1}^{m} \pi R_i ^2 H_i$$
|
||||
|
||||
|
||||
##### 上表面积$S_上$
|
||||
* 第$m$层的上表面积是$S_m - S_{m-1}$
|
||||
* 第$m-1$层的上表面积是$S_{m-1} - S_{m-2}$
|
||||
* ...
|
||||
* 第$2$层的表面积是$S_2-S_1$
|
||||
* 第$1$层的上表面积是$S_1$
|
||||
$$\large \therefore S_{上}=S_m-S_{m-1}+S_{m-1}-S_{m-2}+...+S_2-S_1+S_1=S_m=\pi R_m^2$$
|
||||
|
||||
>**解释**:被盖上的那部分面积,都会由上面的小蛋糕补上
|
||||
|
||||
##### 侧表面积$S_侧$
|
||||
**侧表面积** 就是每个圆柱的侧边面积,也就是一个围成圆柱的矩形面积,**圆的边长** 乘以 **高度** :
|
||||
|
||||
$$\large S_侧=\sum_{i=1}^m 2 \pi R_i H_i$$
|
||||
|
||||
##### 整体表面积$S_整$
|
||||
上表面积 + 侧表面积:
|
||||
$$\large S_{整}=S_{上}+S_{侧}=\pi R_m^2 +\sum_{i=1}^{m}2 \pi R_i H_i $$
|
||||
|
||||
|
||||
#### 3、剪枝与优化技巧
|
||||
|
||||
##### (1) 优化搜索顺序
|
||||
为了 **先搜索的分支数少**,应该 **自大到小** 搜索:
|
||||
* **从第$m$层搜索到第$1$层**
|
||||
* 体积公式中$R_i$是 **平方级别** ,**变化率要高于**$H_i$,先搜$R_i$,再搜$H_i$
|
||||
> **解释**:类比 **[运输小猫](https://www.acwing.com/problem/content/167/)**,大头在前,可以使$dfs$搜索树搜索的层数减少,搜索效率提升
|
||||
|
||||
##### (2) 各层高度和半径的范围
|
||||
> 设第$i$层蛋糕的半径是$R_i$,高度是$H_i$,因为 **半径** 和 **高度** 都是 **整数**,并且$R_i < R_{i+1},H_i < H_{i+1}$
|
||||
> 由于 **最上层** 的蛋糕半径和高度也必须是 **正整数**,所以$R_1$和$H_1$至少是$1$,$R_2$要大于$R_1$,至少是$2$,由此可见:第$i$层蛋糕的 **半径** 和 **高度** 至少是$i$
|
||||
第$i$层的 **高度** 和 **半径** **至少** 是$i$,这就是 **各层高度和半径的最小值**, 这是$R_i,H_i$的 **下界**
|
||||
$R_i < R_{i+1},H_i < H_{i+1}$,这是一个**上界**
|
||||
|
||||
###### (3) 预处理
|
||||
>为了在走一半时,能迅速 **估计** 继续走下去是否能成功,我们可以提前预处理出两个数组:
|
||||
> - $mins[i]$ : **$1$到$i$层侧表面积的最小值**:
|
||||
$$\large mins[i] = mins[i-1] + 2* i * i$$
|
||||
**解释**:我们手中资源是剩余的体积,随时需要知道剩余$1\sim i$这些层中,我们至少需要多少体积,及时发现不合理的分支并及时停止。
|
||||
> - $minv[i]$ : **$1$到$i$层体积的最小值**:
|
||||
$$\large minv[i] = minv[i-1] + i * i * i$$
|
||||
> **解释**:从第$m$层搜到第$u$层总的体积是$v$,而从第$1$层到第$u$层体积至少是$minv[u]$,所以当$v + minv[u] > n$时就说明 **肯定后续会超过体积**$n$,**方案不合法** (**可行性剪枝**)
|
||||
|
||||
##### (4) **找出两个变量之间的制约关系**
|
||||
从第$m$层蛋糕搜到当前的第$u$层时,设已经使用的表面积是$s$,体积是$v$,则从第$u$层到第$1$层留给我们的体积 **最多** 只有$n - v$,所以第$u$层的半径$$\large \pi R_u^2H_u <= n - v$$
|
||||
|
||||
题目很贴心,不想让我们计算$\pi=3.1415926...$这样的小数,而是让我们计算去掉$\pi$后的常数系数,下面的论述中将忽略掉$\pi$:
|
||||
|
||||
$$\large R_u <= \sqrt{((n-v)/H_u)}$$
|
||||
|
||||
$H_u$的 **最小值** 是$u$,取$u$时,右侧相当于取到了 **最大值**,不等式也依然成立,所以
|
||||
|
||||
$$
|
||||
\large \left\{\begin{array}{ll}
|
||||
R_u <= \sqrt{(n-v)/u} & \\
|
||||
H_u <= (n-v) / R_u^2 &
|
||||
\end{array}\right.
|
||||
$$
|
||||
|
||||
> **解释**: $R_u$不是随便想多少就是多少的,受到给定后续大小$n-v$和当前层次$u$的限制,不能大于$\sqrt{(n-v)/u}$。而另一个变量$H_u$也不随便想多少就是多少的,它的受限和$R_u$相关,一旦确定了$R_u$的值,$H_u$的 **上限** 也就确定了。
|
||||
这就是$H_u$和$R_u$的 **另一个上界**,两个变量$R_i$与$H_i$不是孤立的,而是存在制约关系,我们在上面推导的过程,其实就是试图找出两者之间的关联制约关系,这样就可以在$dfs$过程中,及时找到冲突点,及时止损,`return false`。
|
||||
|
||||
既然$R_i$和$H_i$都同时要受两个条件的约束,那么 **他们的上界取需要取 两个上界条件 的最小值**
|
||||
|
||||
|
||||
##### (5) 深搜函数设计
|
||||
|
||||
参数设计:
|
||||
* **$u$:当前是第几层**
|
||||
* **$v$:已经占用的空间**
|
||||
* **$S$:已经获得的表面积**
|
||||
|
||||
令
|
||||
① $ans$是 **之前搜到的最小表面积**
|
||||
② $S$ **=$m$层的上表面积+已完成的各层侧表面积和**
|
||||
|
||||
##### (6) 剪枝策略
|
||||
* **搜索到一半时表面积已超过了目前的最优解**
|
||||
如果$s + mins[u] >= ans$,也就是**当这个方案搜下去的表面积不可能优于小于之前搜到的解**时,应该否定这个方案
|
||||
|
||||
* **表面积和体积之间的制约关系**
|
||||
从第$1$层到第$u$层的 **侧表面积**
|
||||
$$S_{1 \sim u}=\sum_{i=1}^{u}2 \pi R_iH_i$$
|
||||
|
||||
第$1$层到第$u$层的体积($V_u$是指 **从$m$层出发** **到达$u$层** 时,已使用了的体积)
|
||||
$$V_{1\sim u}=n-V_u=\sum_{i=1}^{u}\pi R_i^2Hi$$
|
||||
|
||||
为了找出两者之间的关系,需要构造成同样的部分:**侧表面积** 那个$R_iH_i$,体积这个$R_i^2H_i$,很明显,表面积乘上一个$R_i$再除一个$R_i$就能得到一个和体积相关的东西,但$R_i$目前是不确定的(正在尝试中),我们需要依赖的是前面$u+1$的$R_{u+1}$(本分枝中,前面$u+1$层的$R_{u+1}$已经设定),所以就乘上再除上$R_{u+1}$试试:
|
||||
$$S_{1 \sim u}=\sum_{i=1}^{u}2 \pi R_iH_i=\frac{2\pi}{R_{u+1}}\sum_{i=1}^{u}R_{u+1}R_iH_i$$
|
||||
同时因为$R_{u+1}>R_i$,所以:
|
||||
$$S_{1 \sim u} > \frac{2}{R_{u+1}}\sum_{i=1}^{u} \pi R_i^2Hi$$
|
||||
|
||||
将 **体积等式** 代入:
|
||||
$$S_{1 \sim u} > \frac{2 (n-V_u)}{R_{u+1}}$$
|
||||
|
||||
因此如果$$\large S_总=S_已+S_{1 \sim u}>=S_{ans}$$
|
||||
就是说当前阶段,后面不管怎么整,都肯定不会比目前的最优答案$S_{ans}$更牛$X$,可以剪枝
|
||||
|
||||
即当
|
||||
|
||||
$$S_已+\frac{2(n−V)}{R_{u+1}}>=S_{ans}$$
|
||||
|
||||
时就可以剪枝掉(**最优性剪枝**)
|
||||
|
||||
|
||||
##### (7) 递归出口
|
||||
- 最后递归的边界是$u == 0$,遍历完了所有层蛋糕
|
||||
- 当$v$恰好是$n$时,就可以更新最优解了
|
||||
|
||||
### 四、实现代码
|
||||
```cpp {.line-numbers}
|
||||
#include <bits/stdc++.h>
|
||||
using namespace std;
|
||||
const int INF = 0x3f3f3f3f;
|
||||
const int N = 22;
|
||||
int n; // 待制作的蛋糕的体积为
|
||||
int m; // 蛋糕的层数为 m
|
||||
int ans = INF; // 最小表面积,预求最小,先设最大
|
||||
int mins[N]; // 每一层的最小表面积
|
||||
int minv[N]; // 每一层的最小体积
|
||||
int R[N], H[N]; // 为每一层蛋糕分配的半径和高度
|
||||
|
||||
// u:正在研究第几层
|
||||
// v:已经使用了的体积
|
||||
// s:已经使用了的表面积
|
||||
void dfs(int u, int v, int s) {
|
||||
// 如果来到第u层,发现,已经使用过的体积v,加上第1~u层最小体积minv[u] ,大于整体的体积n,就不用再继续了
|
||||
if (v + minv[u] > n) return;
|
||||
|
||||
// 如果来到第u层,发现,已经使用过的表面各s,加上第u层及以上所有层需要的表面积mins[u]
|
||||
// 大于当前最优解了,就不用再继续研究了,还不如最优解,再研究也是白扯
|
||||
if (s + mins[u] >= ans) return;
|
||||
|
||||
// 第二个最优性剪枝,侧面积和体积不是孤立存在的,是有关联关系的。
|
||||
// 不符合这个关联关系的Ri和Hi是没有必要继续的
|
||||
if (2 * (n - v) / R[u + 1] + s >= ans) return;
|
||||
|
||||
// 摆完所有层蛋糕
|
||||
if (u == 0) {
|
||||
// 如果已经使用的体积等于n,如果是,那么更新答案
|
||||
if (v == n) ans = s;
|
||||
return;
|
||||
}
|
||||
// 枚举每一个可能的半径,同理,半径由大到小
|
||||
for (int r = min(R[u + 1] - 1, (int)sqrt((n - v) / u)); r >= u; r--)
|
||||
for (int h = min(H[u + 1] - 1, (n - v) / r / r); h >= u; h--) {
|
||||
// 如果是第m层,需要加上一个底面积,其它层就只计算侧面积
|
||||
int t = (u == m ? r * r : 0);
|
||||
// 记录下第u层的选择,半径:r,高度:h
|
||||
R[u] = r, H[u] = h; // 因为静态数组可以重复按索引写入,所以不需要回溯
|
||||
// 递归上一层,体积变为v+r*r*h,表面积由s变大为 s + 2 * r * h + t
|
||||
dfs(u - 1, v + r * r * h, s + 2 * r * h + t);
|
||||
}
|
||||
}
|
||||
int main() {
|
||||
cin >> n >> m;
|
||||
// 预处理
|
||||
for (int i = 1; i <= m; i++) {
|
||||
// 第i层,半径最小是i,高度最小是i,否则更小层无法获得整数解
|
||||
// 预处理出每层及上边所有层的最小表面积,看清楚这是一个叠加的关系,不是简单的计算,是递推式
|
||||
mins[i] = mins[i - 1] + 2 * i * i;
|
||||
// 预处理出每层及上边所有层的最小体积
|
||||
minv[i] = minv[i - 1] + i * i * i;
|
||||
}
|
||||
// 初始化为无穷大,防止在计算下层半径高度时取到0
|
||||
R[m + 1] = H[m + 1] = INF;
|
||||
|
||||
// 从下向上进行深搜,按经验来看,这样能减少出发时的分枝数量,效率提升
|
||||
// 从m层出发,此时的体积为0,此时的表面积为0
|
||||
dfs(m, 0, 0);
|
||||
|
||||
// 输出答案
|
||||
if (ans == INF)
|
||||
puts("0");
|
||||
else
|
||||
printf("%d\n", ans);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user