P1120 小木棍 [数据加强版]
输入输出样例
输入 #1复制
9
5 2 1 5 2 1 5 2 1
输出 #1复制
6
结果这道题最后还是没有做出来,也不想再去做了,先把一些收获写下来好了
总结目录
1 本题使用的剪枝技巧,常用剪枝技巧总结
2 提炼出来的剪枝框架
1常用剪枝技巧总结
本题中,搜索的一个关键是如何确定搜索的空间,也就是我们确定了什么,要去搜索什么。在这题里面,我们需要确定的是搜索的下限,即木棒的最长的长度,木棒的上限,即木棒的长度和。然后去遍历它。这样遍历之后就可以进行具体的搜索了。
暴力搜索会导致TLE,因此要使用剪枝。这里总结一下本题的一些剪枝的思想:
1 如果找到了答案,使用一个bool flag变量,在dfs中,在开头先判断这个变量,以此进行剪枝
2 根据题目的特点确定优先搜索的顺序。比如在本题中,我们对木棒的长度进行排序,我们认为从最长的开始搜索会更快的搜到我们的答案。这个思路在 P1074 靶形数独 中也是有用到过的。
3 根据题目的特点,在遍历长度的时候,我们需要考虑是否整除,否则是没有遍历的意义的,即对dfs参数进行筛选。这个其实相当于我在 滑雪 那题里面我自己做的一个预处理。
4 使用跳跃枚举。一般的枚举都是从1~n,i++。可以使用一个打表的方法,即用数组记录下来,每次记录下循环跳转的路径即可。
5 基于排序后的优化,可以引申出其他的优化。
5.1 预处理相同的木棒长度,然后再for循环中直接跳转到下一个序号。
5.2 使用二分法进行查找。 查找的过程,要考虑是找恰好,还是左边界,还是右边界;在边界内还是边界外。这个可以参考我的文章
二分查找练习——leetcode 34. 在排序数组中查找元素的第一个和最后一个位置
https://blog.csdn.net/qq_35299223/article/details/101994242
6 基于最优化情况的剪枝。这个情况是指,对于特定的情况,我们可以确定当前的方案已经是最优了。如果当前的方案已经是错误的,那么我们直接返回上一层即可,不需要再进行其他的枚举了。 这个枚举的框架在下面说明。
2 最优化剪枝框架
dfs(){
if(达到返回要求){
flag=true;
return;
}
if(flag==true){
return;//找到了答案,直接剪枝
}
for( i= findbound();i<=n; i=Nextind()){
//上面两个i的初始化以及i的跳跃是在有序情况下可以考虑的优化
if(属于最优情况){
标记
dfs深搜更深一层
清除标记
break或者return直接返回;//因为当前已经是最优化了,因此考虑直接跳出
}
else if(不属于最优化情况){
标记
dfs深搜更深一层
清除标记
//此处不需要跳出,直接进行本层的循环
//也可以根据情况添加其他的剪枝
}
}
}
暴力dfs代码
#include<iostream>
#include<algorithm>
#define maxsize 100
using namespace std;
int n;
int rod[maxsize];
int used[maxsize];
int maxlen;
int minres;
int cnt;
bool flag = false;
bool cmp(int a, int b) {
return a > b;
}
void dfs(int curcnt, int left, int target) {
//正在收入第cnt根木棒,当前距离target长度还有left长
if (curcnt > cnt&&left==target) {
flag = true;
minres = target;
return;//如果已经收完所有木棒,并且收完最后一根后长度恰好,则完成搜索
}
for (int i = 1; i <= cnt; i++) {
if (flag == true) {
break;//剪枝
}
if (used[i] != 1&&left>=rod[i]) {
used[i] = 1;
if (left - rod[i] == 0) {
dfs(curcnt + 1, target, target);//如果恰好接上一根,那么要开始另外一根
}
else if (left - rod[i] > 0) {
dfs(curcnt + 1, left - rod[i], target);//如果无法接上一根,那么继续接
}
used[i] = 0;
}
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
int len;
cin >> len;
if (len <= 50) {
cnt++;
maxlen = max(maxlen, len);//求最大值,方便后续计算
rod[cnt] = len;
}
}
int lensum=0;
for (int i = 1; i <= cnt; i++) {
lensum += rod[i];
}
sort(rod + 1, rod + 1 + cnt, cmp);
for (int tmplen = maxlen; tmplen <= lensum; tmplen++) {
dfs(1, tmplen, tmplen);
}
cout << minres;
return 0;
}
代码 66%
#include<iostream>
#include<algorithm>
#define maxsize 100
using namespace std;
int n;
int rod[maxsize];
int used[maxsize];
int Next[maxsize];
int NextLeftboard[maxsize];
int StartArr[maxsize];
int maxlen;
int minres;
int cnt;
bool flag = false;
bool cmp(int a, int b) {
return a > b;
}
int findrightboard(int arr[],int arrlen, int target) {
//返回降序数组中第一个小于target的数的序号
//这对于降序数组来说,找到第一个小于的,相当于找右边界,而且是找区间外的,因此不-1
//如果找第一个大于的,相当于找左边界
int left = 1;
int right = arrlen + 1;
while (left < right) {
int mid = (left + right) / 2;
if (arr[mid] == target) {
left = mid + 1;
}
else if (arr[mid] > target) {
left = mid+1;
}
else if (arr[mid] < target) {
right = mid;
}
}
if (right == (arrlen+1))return arrlen;
else return right;
}
int findleftboard(int arr[], int arrlen, int target) {
//找到降序数组第一个大于target的数
//这是是找target区间的左边界,而且是在区间外的,这种情况下画数轴判断,需要-1
int left = 1;
int right = arrlen + 1;
while (left < right) {
int mid = (left + right) / 2;
if (arr[mid] == target) {
right = mid;
}
else if (arr[mid] > target) {
left = mid + 1;
}
else if (arr[mid] < target) {
right = mid;
}
}
//跳出时left==right,由于返回-1,要特判0
if (left == 1)return 1;
return (left - 1);
}
void GetNextLeftboard() {
//NextLeftboard[i]为rod[]数组中第一个大于rod[i]长度的木棒长度的下标
for (int i = 1; i <= cnt; i++) {
NextLeftboard[i] = findleftboard(rod, cnt, rod[i]);
}
}
void GetNextArr() {
//利用排序后的rod[]数组,得到next[]数组
//next[i]为rod[]数组中第一个小于rod[i]长度的木棒长度的下标
for (int i = 1; i <= cnt; i++) {
Next[i] = findrightboard(rod, cnt, rod[i]);
}
}
//----------------------正文代码-----------------------------//
int FindNextInd(int arr[], int arrlen, int target,int curindex) {
//找到在降序数组arr[]中,第一个长度小于target的下标
//寻找右边界,是边界外,返回right
//要求左边界从curindex进行跳跃,而不是每次都从[1,cnt]进行二分,而是从[i,cnt]进行二分
//如果考虑到i已经搜索过,代码中可以从i+1开始进行,但是这样要对left特判越界,此处舍弃
//由于在for循环中,跳出循环要超过cnt,因此此处的代码还需要修改
int left = curindex;
int right = arrlen + 1;
while (left < right) {
int mid = (left + right) / 2;
if (arr[mid] == target) {
left = mid + 1;
}
else if (arr[mid] > target) {
left = mid + 1;
}
else if (arr[mid] < target) {
right = mid;
}
}
return right;
}
int FindStartInd(int arr[], int arrlen, int leftlen,int startind) {
//这是是用来初始化枚举的开始,令查找从<=leftlen的下标开始查找合适的长度
//在降序数组arr[]中,找到第一个小于等于leftlen的数
//这里可以寻找左边界,边界内,返回left,重复的数字区间在dfs中更新i的跳跃的时候处理即可
int left = startind;//定义左边界,剪枝
int right = arrlen + 1;
while (left < right) {
int mid = (left + right) / 2;
if (arr[mid] == leftlen) {
right = mid;
}
else if (arr[mid] < leftlen) {
right = mid;
}
else if (arr[mid] > leftlen) {
left = mid + 1;
}
}
if (left == arrlen + 1)return arrlen;
return left;
}
void dfs(int curcnt, int left, int target) {
//正在收入第cnt根木棒,当前距离target长度还有left长
if (curcnt > cnt&&left==target) {
flag = true;
minres = target;
return;//如果已经收完所有木棒,并且收完最后一根后长度恰好,则完成搜索
}
if (flag == true) {
return;//剪枝
}
for (int i = 1; i <= cnt; i++) {
if (used[i] != 1&&left>=rod[i]) {
used[i] = 1;
if (left - rod[i] == 0) {
dfs(curcnt + 1, target, target);//如果恰好接上一根,那么要开始另外一根
}
else if (left - rod[i] > 0) {
dfs(curcnt + 1, left - rod[i], target);//如果无法接上一根,那么继续接
}
used[i] = 0;
}
}
}
void dfs2(int curcnt, int leftlen, int target) {
if (curcnt > cnt&&leftlen == target) {
flag = true;
minres = target;
return;//已经收完所有木棒,并且最后一根后长度恰好,则完成搜索
}
if (flag == true) {
return;//找到结果直接跳过后续搜索,最普通的剪枝
}
for (int i =1; i <= cnt;i++) {
//i初始化为<=leftlen的下标,不再从1开始枚举;
//单次枚举失败时,i从当前下标开始进行跳跃,而不是单纯i++;
//以上2个剪枝是基于有序的数组才能实现
if (used[i] != 1) {
if (leftlen-rod[i]==0) {
//此为当前最优情况,如果该情况失败了,直接返回,不再进行同层的跳跃枚举
used[i] = 1;
dfs2(curcnt + 1, target, target);
used[i] = 0;//如果走到这一步就说明已经失败了直接跳出循环
break;//此处使用break或者return跳出都可以,我建议用break;
}
if (leftlen - rod[i] > 0) {
used[i] = 1;
dfs2(curcnt + 1, leftlen - rod[i], target);
used[i] = 0;//此处失败了,还可以继续进行枚举,不用跳出
if (leftlen == target)break;//这个剪枝的原因是因为没有拼过,肯定要拼,那么最长的肯定能用上,这里不行就肯定不行了
while (rod[i] == rod[i + 1]&&i<cnt) {//一种常用的跳跃循环
i++;//虽然继续枚举,但是我们要找不同的值
//当前的i是不可行的,我们遍历知道i+1和i不同即可
}
}
}
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
int len;
cin >> len;
if (len <= 50) {
cnt++;
maxlen = max(maxlen, len);//求最大值,方便后续计算
rod[cnt] = len;
}
}
int lensum=0;
for (int i = 1; i <= cnt; i++) {
lensum += rod[i];
}
sort(rod + 1, rod + 1 + cnt, cmp);
for (int tmplen = maxlen; tmplen <= lensum; tmplen++) {
if (lensum%tmplen != 0)continue;
dfs2(1,tmplen, tmplen);
}
cout << minres;
return 0;
}