目录
一. DFS的实现
int dfs(int t)
{
if(满足输出条件) //dfs出口
{
输出解;
}
else
{
for(int i=1;i<=尝试方法数;i++)
if(满足进一步搜索条件)
{
为进一步搜索所需要的状态打上标记; //记忆化
dfs(t+1);
恢复到打标记前的状态; //也就是说:回溯一步
}
}
}
【需要注意的地方】
1.第一个if是符合输出解的条件,第二个if是符合进一步搜索的条件。
2.下一步搜索时,不是使用return search(t+1),直接search(t+1)。
3.for循环之后的if可以是多个。
4.for循环边界,例如:
- 方向是四个,那么边界肯定就是4;(注意,数组一般从0开始)
- 素数环需要尝试1至20,那么边界就是20。
二. 剪枝技巧的实现
1.可行性剪枝。
如果当前条件不合法就不再继续搜索,直接return。又称“上下界剪枝”,一般的搜索都会加上。
dfs(int x){
if(x>n)return;
if(!check1(x))return;
....
return;
}
2.最优性剪枝。
如果当前条件所花费的代价已经超过了当前搜到的最优解,那么剩下的搜索就可以剪掉。
我们利用某个函数估计出此时条件下答案的当前最值,继续下一步判断。
long long ans=987474477434487ll;
... Dfs(int x,...){
if(x... && ...){ ans=....; return ...;}
if(check2(x)>=ans) return ...; //最优性剪枝
for(int i=1;...;++i){
vis[...]=1;
dfs(...);
vis[...]=0;
}
}
一般实现:在搜索取和最大值时,如果后面的全部取最大仍然不比当前答案大就可以返回。
在搜和最小时同理,可以预处理后缀最大/最小和进行快速查询。
3.记忆化搜索。
如果对于相同情况下必定答案相同,就可以把这个情况的答案值存储下来,以后再次搜到可以直接调用。
注意不能搜出环来,不同情况间不能互相依赖。或者是排除等效冗余的情况。
long long ans=987474477434487ll;
... Dfs(int x,...){
if(x... && ...){ ans=....; return ...;}
if(vis[x]!=0) return f[x]; vis[x]=1; //记忆化剪枝
for(int i=1;...;++i){
vis[...]=1;
dfs(...);
vis[...]=0;
f[x]=...;
}
}
4.搜索顺序剪枝
在一些迷宫题,网格题,或者其他搜索中可以贪心的题,搜索顺序显得十分重要。
在迷宫、网格类的题目中,以左上->右下为例,右下左上就明显比左上右下优秀。
在一些推断搜索题中,从已知信息最多的地方开始搜索显然更加优秀。
在一些题中,先搜某个值大的,再搜某个值小的(比如树的度数,产生答案的预计(A*)),速度明显更快。
(原帖来自https://www.cnblogs.com/fenghaoran/p/6391016.html)
【例题】
在古埃及,人们使用单位分数的和(形如1/a的, a是自然数)表示一切有理数。
如:2/3=1/2+1/6,但不允许2/3=1/3+1/3,因为加数中有相同的。
对于一个分数a/b,表示方法有很多种,但是哪种最好呢?
首先,加数少的比加数多的好,其次,加数个数相同的,最小的分数越大越 好。
如:19/45=1/3 + 1/12 + 1/180 19/45=1/3 + 1/15 + 1/45 19/45=1/3 + 1/18 + 1/30,
19/45=1/4 + 1/6 + 1/180 19/45=1/5 + 1/6 + 1/18. 最好的是最后一种,因为1/18最大。
给出a,b(0<a<b<1000),编程计算最好的表达方式。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define LL long long int
using namespace std;
LL a,b,depth,FLAG=1,zZ[101010],Ans[101010],Maxx=10101000;
LL gcd(LL a,LL b){return b>0?gcd(b,a%b):a;} //辗转相除法求最大公约数
void dfs(LL now,LL a,LL b,LL last,LL depth){
if(now==depth-1){
if(a!=1)return;
if(b<Maxx && b>last){
zZ[now+1]=b;FLAG=0;Maxx=b;
for(LL i=1;i<=now+1;++i){Ans[i]=zZ[i];}
}
return;
}
if(a*(last+1)>=b*(depth-now) || last>Maxx || a==0)return; //第一个是可行性剪枝,是个十字相乘式,建议移项看
for(LL i=last+1,K=(depth-now)*b/a;i<K;++i)
{
LL newa=a*i-b,newb=b*i,G=gcd(newb,newa);
newa/=G,newb/=G;zZ[now+1]=i;
dfs(now+1,newa,newb,i,depth);zZ[now+1]=0;
}
}
int main() {
scanf("%lld %lld",&a,&b);
if(a==1){printf("%lld",b);return 0;}
for(int i=2;FLAG;++i)dfs(0,a,b,(b/a),i); //迭代搜索,i为深度
for(int i=1;Ans[i]!=0;++i)printf("%lld ",Ans[i]);
return 0;
}
这道题可行性和最优性剪枝都要加,最后一个是因为要除a,是零就得剪掉。搜索顺序是按分母从小到大枚举的。
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
/*【Sticks】
题意:给出n根小棒的长度stick[i],已知这n根小棒原本由若干根长度相同的长木棒(原棒)分解而来。求出原棒的最小可能长度。
思路:dfs+剪枝。蛮经典的题目,重点在于dfs剪枝的设计。先说先具体的实现:求出总长度sum和小棒最长的长度max,
则原棒可能的长度必在max~sum之间,然后从小到大枚举max~sum之间能被sum整除的长度len,
用dfs求出所有的小棒能否拼凑成这个长度,如果可以,第一个len就是答案。
下面就是关键的了,就是这道题dfs的实现和剪枝的设计:
1.以一个小棒为开头,用dfs看看能否把这个小棒拼凑成len长,用vis[i]记录下用过的小棒,然后继续以另外一个小棒为开头,以此类推。
2.小棒的长度从大到小排序。3.如果当前最长的小棒不能拼成len长,那么就返回前一步,不用再继续搜索这一种情况。
4.最重要的,就是比如说17,9,9,9,9,8,8,5,2……17与第一个9组合之后dfs发现不能拼成len,那么17就不和后面9组合了,而直接和8开始组合。 */
const int Max = 65;
int n, len, stick[Max];
bool flag, vis[Max];
bool cmp(int a,int b) { return a>b; }
void dfs(int dep, int now_len, int u){ // dep为当前已被用过的小棒数,u为当前要处理的小棒。
if(flag) return;
if(now_len == 0){ // 当前长度为0,寻找下一个当前最长小棒。
int k = 0;
while(vis[k]) k ++; // 寻找第一个当前最长小棒。
vis[k] = true;
dfs(dep + 1, stick[k], k + 1);
vis[k] = false;
return;
}
if(now_len == len){ // 当前长度为len,即又拼凑成了一根原棒。
if(dep == n) flag = true; // 完成的标志:所有的n根小棒都有拼到了。
else dfs(dep, 0, 0);
return;
}
for(int i = u; i < n; i ++)
if(!vis[i] && now_len + stick[i] <= len){
if(!vis[i-1] && stick[i] == stick[i-1]) continue; // 不重复搜索:最重要的剪枝。
vis[i] = true;
dfs(dep + 1, now_len + stick[i], i + 1);
vis[i] = false;
}
}
int main(){
while(scanf("%d", &n) && n != 0){
int sum = 0;
flag = false;
for(int i = 0; i < n; i ++){
scanf("%d", &stick[i]);
sum += stick[i];
}
sort(stick,stick+n,cmp); //从大到小排序。
for(len = stick[0]; len < sum; len ++)
if(sum % len == 0){ // 枚举能被sum整除的长度。
memset(vis, 0, sizeof(vis));
dfs(0, 0, 0);
if(flag) break;
}
printf("%d\n", len);
}
return 0;
}
——时间划过风的轨迹,那个少年,还在等你。