剪枝与优化的方法
1.优化搜索顺序
大部分情况下,我们应该优先搜索分支较少的节点
2.排除等效冗余
3.可行性剪枝
4.最优性剪枝
5.记忆化搜索(DP)
1.小猫爬山
题目https://www.acwing.com/problem/content/description/167/
1.优化搜索顺序-》从大到小排序进行搜索
2.可行性剪枝-》假如该组总和+当前数则不可行
3.最优性剪枝-》因为要答案最小,假如搜索过程中已经大于我的答案了,说明后面的搜索都没用
#include<bits/stdc++.h>
using namespace std;
const int N=20;
int n,W;
int w[N],ans=N;
int group[N];
void dfs(int u,int k)//u是第几个数,k是有多少组
{
if(k>=ans) return;//最优性剪枝
if(u==n)//假如搜完了所有点
{
ans=k;
return;
}
for(int i=0;i<k;i++)
if(group[i]+w[u]<=W)//可行性剪枝
{
group[i]+=w[u];//该组加上他
dfs(u+1,k);//继续处理下一个数,组数不变
group[i]-=w[u];//恢复现场
}
//新开一个组
group[k]+=w[u];//该组加上他
dfs(u+1,k+1);//继续处理下一组,组数加一
group[k]-=w[u];//恢复现场
}
int main()
{
cin>>n>>W;
for(int i=0;i<n;i++) cin>>w[i];
//下面两步是优化搜索顺序
sort(w,w+n);
//从大到小排序
reverse(w,w+n);
dfs(0,0);
cout<<ans<<endl;
return 0;
}
2.数独
题目 https://www.acwing.com/problem/content/description/168/
1.优化搜索顺序->选择分支较少的点
2.可行性剪枝-》不能与行、列和九宫格重复
3.最优性剪枝-》位运算优化,用来判断那个位置能填的数有哪些
#include<bits/stdc++.h>
using namespace std;
const int N=9,M=1<<N;
int ones[M],mark[M];
char str[100];
int row[N],col[N],cell[3][3];
void init()//赋予状态
{
//一开始所有行列九宫格都没数字
for(int i=0;i<N;i++) col[i]=row[i]=M-1;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
cell[i][j]=M-1;
}
void draw(int x,int y,int t,bool is_set)//用于在str上画数字
{
if(is_set) str[x*N+y]='1'+t;//假如是要画数字的,则在str上赋值
else str[x*N+y]='.';//反之是空位的
int v=1<<t;//用来获取该数字的二进制
if(!is_set) v=-v;//假如是空位,则为-v
//-=v说明数字t在行和列和九宫格都用过了
row[x]-=v;
col[y]-=v;
cell[x/3][y/3]-=v;
}
int get(int x,int y)//用来获取行和列和九宫格没出现过的数
{
return row[x]&col[y]&cell[x/3][y/3];
}
int lowbit(int x)//用来获取某个数字第一个1
{
return x&-x;
}
bool dfs(int cnt)
{
if(!cnt) 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];
x=i,y=j;
}
}
int state=get(x,y);//这个状态是所有空格中能填的数字的最少
for(int i=state;i;i-=lowbit(i))//开始填这个状态能填的数
{
int t=mark[lowbit(i)];//获取该数字是什么
draw(x,y,t,true);//填上这个数字
if(dfs(cnt-1)) return true;
draw(x,y,t,false);//恢复现场
}
return false;
}
int main()
{
//初始化
for(int i=0;i<N;i++) mark[1<<i]=i;//用来算2的n次方
for(int i=0;i<1<<N;i++)//用来求某个数i的个数
for(int j=0;j<N;j++)
ones[i]+=i>>j&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);//在str上标记一下
}
else cnt++;//反之是要填的数
dfs(cnt);//dfs一遍要填的所有数
puts(str);//输出填完后的str
}
return 0;
}
3.木棒
题目https://www.acwing.com/problem/content/169/
剪枝1.最优性剪枝-》只有当长度能被总和整除时才合法
剪枝2.优化搜索顺序-》从大到小枚举
剪枝3.排除等效元素
3-1 按照组合的方式枚举
3-2 假如目前的木棍加到当前组失败了,则直接略过后面长度相等的木棍
3-3 假如木棍第一根就失败了,则一定会失败
3-4 假如木棍最后一根失败了,则一定会失败
#include<bits/stdc++.h>
using namespace std;
const int N=70;
int n,len,sum;
int w[N];
bool st[N];
bool dfs(int u,int s,int start)//u表示有第几组,s表示该组的总和,start表示从第几个开始搜
{
if(u*len==sum) return true;//假如组数乘长度已经等于总和了,说明这种长度符合
if(s==len) return dfs(u+1,0,0);//假如该组已经满了,则新开一组继续搜
//剪枝3-1,i从start开始
for(int i=start;i<n;i++)
{
if(st[i]) continue;//假如用过
if(s+w[i]>len) continue;//可行性剪枝
st[i]=true;//标记用过
if(dfs(u,s+w[i],i+1)) return true;//则放进该组里,继续搜索
st[i]=false;//回溯,恢复现场
//剪枝3-3
if(!s) return false;
//剪枝3-4
if(s+w[i]==len) return false;
//剪枝3-2
int j=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;
for(int i=0;i<n;i++) cin>>w[i],sum+=w[i];
//剪枝2,优化搜索顺序
sort(w,w+n);
reverse(w,w+n);
len=w[0];
//最大一组就是自己sum
while(1)
{
//剪枝1
if(sum%len==0&&dfs(0,0,0))//假如这个长度符合
{
cout<<len<<endl;
break;
}
len++;
if(len>sum) break;//假如已经超了
}
}
return 0;
}
4.生日蛋糕
题目http://ybt.ssoier.cn:8088/problem_show.php?pid=1441
#include<bits/stdc++.h>
using namespace std;
const int N=25,INF=1e9;
int n,m;
int minv[N],mins[N];
int R[N],H[N];
int ans=INF;
void dfs(int u,int v,int s)
{
if(v+minv[u]>n) return;//假如体积已经大于最大体积了
if(s+mins[u]>=ans) return;//假如已经大于等于当前答案了,后面在做没意义了
if(s+2*(n-v)/R[u+1]>=ans) return;//推理优化
if(!u)//假如搜到了最后一个数
{
if(v==n) ans=s;//假如体积刚好符合,则更新一下最小值
return;
}
for(int r=min(R[u+1]-1,(int)sqrt(n-v));r>=u;r--)//枚举合法的r
for(int h=min(H[u+1]-1,(n-v)/r/r);h>=u;h--)//枚举合法的h
{
int t=0;
if(u==m) t=r*r;//表面积的增加量
R[u]=r,H[u]=h;//该点的r是r,h是h
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;
}
R[m+1]=H[m+1]=INF;
dfs(m,0,0);//从底往上搜索
if(ans!=INF) cout<<ans<<endl;//假如有方案
else cout<<0<<endl;//假如没方案输出0
return 0;
}