目录
DFS剪枝与优化
0)剪枝优化策略
1)小猫爬山
2)数独
3)木棒
4)生日蛋糕(数学剪枝!!!)
来自yxc算法提高课 的大佬:深度优先搜索(DFS)的剪枝方式 - AcWing
输入概要
第一行为 小猫的总数n 缆车的最大承受重量
接下来的n行为 :各个小猫的重量
核心思路:
剪枝优化:
完整代码:
#include
#include
using namespace std;
const int N = 2e1;
int cat[N], cab[N];
int n, w;
int ans;
bool cmp(int a, int b)
{
return a > b;
}
void dfs(int now, int cnt)//当前遍历到的小猫编号 ,当前开的缆车个数
{
if (cnt >= ans) {
return;
}
if (now == n + 1)
{
ans = min(ans, cnt);
return;
}
//尝试分配到已经租用的缆车上
for (int i = 1; i <= cnt; i++)
{ //分配到已租用缆车
if (cab[i] + cat[now] <= w)
{
cab[i] += cat[now];
dfs(now + 1, cnt);
cab[i] -= cat[now]; //还原
}
}
// 新开一辆缆车
cab[cnt + 1] = cat[now];
dfs(now + 1, cnt + 1);
cab[cnt + 1] = 0;
}
int main()
{
cin >> n >> w;
for (int i = 1; i <= n; i++) cin >> cat[i];
sort(cat + 1, cat + 1 + n, cmp);
ans = n;
dfs(1, 0);
cout << ans << endl;
return 0;
}
核心思路:
剪枝优化::
毫无疑问:上述的这个步骤会超时,因此需要剪枝优化
对于一个未填数字的'.'处 坐标为(x,y)
它的行上 已经 有了 2 4 7 这三个数字
它的列上 已经 有了 2 3 9 这三个数字
它的3*3宫格上 已经 有了 1 2 5 这三个数字
9 8 7 6 5 4 3 2 1 0
高位--------->>>>>>>>>低位
row[x]=1101101011
col[y]=0111110011
ceil[x/3][y/3]=1111011001
0101000001
0表示这个点上有数字
将它们按位 & 的结果是:
所以此时可选的数字只有 8 和 6 因为数独中数字的范围是1~9(所以二进制的0位去掉)
因此对于这个二进制优化,我们又得下一番功夫......
二进制优化
好了,这道题,其实都是预处理剪枝,等效冗余的情况下采取了,二进制优化,分析好了,那么码代码就不是什么难事了
#include
#include
#include
using namespace std;
const int N = 9, M = 1 << N;
int ones[M], map[M];
int row[N], col[N], cell[3][3];
char str[100];
void init()//初始状态每一位除了第一位 都是1 -->>因为第一位为0 数独中不为0
{
for (int i = 0; i < N; i ++ )
row[i] = col[i] = (1 << N) - 1;
for (int i = 0; i < 3; i ++ )
for (int j = 0; j < 3; j ++ )
cell[i][j] = (1 << N) - 1;
}
void draw(int x, int y, int t, bool is_set)//在(x,y)坐标上放入数字t
{
if (is_set) str[x * N + y] = '1' + t;
else str[x * N + y] = '.';
int v = 1 << t;//转为二进制
if (!is_set) v = -v;
row[x] -= v;//行的二进制该数字减去这个1
col[y] -= v;//列的二进制该数字进去这个1
cell[x / 3][y / 3] -= v;//同理
}
int lowbit(int x)//返回 pow(2,最后一位1的位置)
{
return x & -x;
}
int get(int x, int y)//计算可枚举的数字
{
return row[x] & col[y] & cell[x / 3][y / 3];
}
bool dfs(int cnt)//空格的数量
{
if (!cnt) return true;//没有空格了,证明已经被填满了-->>return true
int minv = 10;
int x, y;
for (int i = 0; i < N; i ++ )//找到可填写最少种类数字的空格
for (int j = 0; j < N; j ++ )
if (str[i * N + j] == '.')//该位置该填数字
{
int state = get(i, j);//接受
if (ones[state] < minv)
{
minv = ones[state];//ones[state]计算该数字的1的个数 -->>也就是可填写多少个数字
x = i, y = j;//记录位置
}
}
int state = get(x, y);
for (int i = state; i; i -= lowbit(i))
{
int t = map[lowbit(i)];//最后一个1的位置:也就是枚举到的该数字:1~9
draw(x, y, t, true);//放入
if (dfs(cnt - 1)) return true;
draw(x, y, t, false);
}
return false;
}
int main()
{
map存的是 pow(i,2)==i -->>也就是快速得到一个数 M 的log2(M)的结果
for (int i = 0; i < N; i ++ ) map[1 << i] = i;//预处理
for (int i = 0; i < 1 << N; i ++ )//枚举0~2^9次方的数字中1的个数
for (int j = 0; j < N; j ++ )//枚举宫格中一个数的9个位数
ones[i] += i >> j & 1;//计算该数中在二进制下 有多少个1
while (cin >> str, str[0] != 'e')
{
init();//初始化
int cnt = 0;
for (int i = 0, k = 0; i < N; i ++ )
for (int j = 0; j < N; j ++, k ++ )
if (str[k] != '.')
{
int t = str[k] - '1';//已经填好的数字
draw(i, j, t, true);
}
else cnt ++ ;//. 说明要填入数字,cnt:有多少个空的待填入数字
dfs(cnt);
puts(str);
}
return 0;
}
样例解释:
第一行输入: 小木棍的个数
第二行输入:小木棍的长度
输出:木棒的最小长度(注意是等长的木棒)
对于第一组样例:
(5+1) (5+1) (5+1) (2+2+2) 木棒的最小长度为 6
对于第二组样例:
(1+4) (2+3) 木棒的最小长度为:5
核心思路:
大木棒的总长度==等长的木棒拼接而成
参考自:AcWing 167. 木棒(详细证明,图解版) - AcWing
剪枝优化:
这个代码说实话,还是很难写的(对我目前来说)
#include
#include
#include
using namespace std;
const int N = 70;
int w[N], sum, length, n;
bool st[N];
//第u组,part第u组的已有长度,start表示第u组的枚举位置;
bool dfs(int u, int part, int start)
{
if (u * length == sum) return true;//如果总长度已经达到了,return true
if (part == length) return dfs(u + 1, 0, 0);//下一组
for (int i = start; i <= n; i++)
{
if (st[i]) continue;//已经用过了
if (w[i] + part > length) continue;//原来的+遍历到的>大于该组长度
st[i] = true;//标记已经使用
if (dfs(u, w[i] + part, i + 1)) return true; //因为前i个棍子都在第u组枚举了, 所以直接从第i + 1根棍子开始枚举
st[i] = false;//返回上层分支时要恢复现场(枚举该组不选择第i根根子的方案)
if (!part || w[i] + part == length) return false;//如果第一根失败了或者最后一根失败了,就一定失败
int j = i;//如果i失败了,那么长度跟i一样的棍子也一定失败
while (j <= n && w[j] == w[i]) j++;
i = j - 1;
}
return false;//枚举完了还没有成功,就返回失败
}
int main()
{
while (cin >> n,n)
{
memset(st, 0, sizeof st);
sum = 0, length = 1;//sum:所有大木棍的总和,length:单根大木棍的长度
for (int i = 1; i <= n; i++)
{
cin >> w[i];
sum += w[i];//总和
}
//倒着排序,减少分支
sort(w + 1, w + n + 1);
reverse(w + 1, w + n + 1);
while (1)//枚举length的长度
{
if (sum % length == 0 && dfs(0, 0, 0))
{
cout << length << endl;
break;
}
length++;
}
}
return 0;
}
#include
#include
using namespace std;
const int N = 24, INF = 1e9;
int n, m;
int minv[N], mins[N];
int res = INF;
//记录每层的半径和高,因为会用到上一层的高度
int R[N], H[N];
//u当前层次,v当前处理的体积和,s当前处理的面积和
void dfs(int u, int v, int s)
{
if(v + minv[u] > n) return;
if(s + mins[u] >= res) return;
if (s + 2 * (n - v) / R[u + 1] >= res) return;
if(!u)
{
if(v == n) res = s;
return;
}
//搜索顺序,先R后H,从大到小
for(int r = min(R[u + 1] - 1,(int)sqrt((n - v - minv[u - 1]) / u)); r >= u; r--)
for(int h = min(H[u + 1] - 1, (n - v - minv[u - 1]) / r / r); h >= u; h--)
{
H[u] = h, R[u] = r;
//最底层的时候需要加上r*r
int t = u == m ? r * r : 0;
dfs(u - 1, v + r * r * h, s + 2 * r * h + t);
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= m; i++)
{
minv[i] = minv[i - 1] + i * i * i;
mins[i] = mins[i - 1] + 2 * i * i;
}
//m+1层不存在,作为哨兵,减少边界情况的判断
R[m + 1] = H[m + 1] = INF;
dfs(m, 0, 0);
if(res == INF) res = 0;
cout << res << endl;
return 0;
}
作者:random_srand
链接:https://www.acwing.com/solution/content/31876/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
样例解释:
题解参考自:AcWing 168. 生日蛋糕【图解+推导】 - AcWing
原谅我:这个等我有空的时候再写() 要补高数作业了
#include
#include
using namespace std;
const int N = 24, INF = 1e9;
int n, m;
int minv[N], mins[N];
int res = INF;
//记录每层的半径和高,因为会用到上一层的高度
int R[N], H[N];
//u当前层次,v当前处理的体积和,s当前处理的面积和
void dfs(int u, int v, int s)
{
if(v + minv[u] > n) return;
if(s + mins[u] >= res) return;
if (s + 2 * (n - v) / R[u + 1] >= res) return;
if(!u)
{
if(v == n) res = s;
return;
}
//搜索顺序,先R后H,从大到小
for(int r = min(R[u + 1] - 1,(int)sqrt((n - v - minv[u - 1]) / u)); r >= u; r--)
for(int h = min(H[u + 1] - 1, (n - v - minv[u - 1]) / r / r); h >= u; h--)
{
H[u] = h, R[u] = r;
//最底层的时候需要加上r*r
int t = u == m ? r * r : 0;
dfs(u - 1, v + r * r * h, s + 2 * r * h + t);
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= m; i++)
{
minv[i] = minv[i - 1] + i * i * i;
mins[i] = mins[i - 1] + 2 * i * i;
}
//m+1层不存在,作为哨兵,减少边界情况的判断
R[m + 1] = H[m + 1] = INF;
dfs(m, 0, 0);
if(res == INF) res = 0;
cout << res << endl;
return 0;
}
作者:random_srand
链接:https://www.acwing.com/solution/content/31876/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。