搜索——DFS之剪枝与优化

常见优化思路

  1. 优化搜索顺序
    大多数情况下,优先搜索分支较少的点(排序从大到小)
  2. 排除等效冗余
    组合的形式,或者相等重复的情况
  3. 可行性剪枝
    根据题意进行的剪枝
  4. 最优化剪枝
    根据答案来进行剪枝
  5. 记忆化搜索

小猫爬山

原题连接

这里是引用

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef pair<int, int>pii;

const int N = 20;
int n,m;
int a[N];
int ans=N,len=0;
int g[N];
void dfs(int u){
    if(len>=ans) return;
    if(u==n){
        ans=min(ans,len);
        return;
    }
    for(int i=0;i<len;i++){
        if(a[u]+g[i]<=m){
            g[i]=g[i]+a[u];
            dfs(u+1);
            g[i]=g[i]-a[u];
        }
    }
    //新开一个组的技巧
    g[len++]+=a[u];
    dfs(u+1);
    g[--len]=0;
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>n>>m;
    for(int i=0;i<n;i++) cin>>a[i];
    sort(a,a+n);
    reverse(a,a+n);//从最大开始,优化,不然会tle
    dfs(0);
    cout<<ans;
    return 0;
}

总结

  1. 这种题型很常见,放到已有组或者新开一个组
  2. 运用了优化搜索顺序,可行性,最优化剪枝

数独

原题链接

这里是引用

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
// #include<map>//[Error] reference to 'map' is ambiguous,变量名冲突
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f

using namespace std;
typedef pair<int, int>pii;

const int N = 9,M=1<<9;
int ones[M],map[M];//ones状态有多少个1。map[i]=log2(i)
int row[N],col[N],cell[3][3];
char str[100];

//初始状态,每行每列每个3*3方格,都没放数,即状态都是111\111\111,表示没放987654321
void init(){
    for(int i=0;i<N;i++) col[i]=row[i]=(1<<N)-1;
    for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            cell[i][j]=(1<<N)-1;
}

//在(i,j)这个位置放或者拿走i时,更新状态   (真实是i+1,因为从1开始)
void draw(int i,int j,int t,bool is_set){//true是放,即将对应位置变为0
    if(is_set) str[i*N+j]='1'+t;
    else str[i*N+j]='.';
    
    int v=1<<t;
    if(!is_set) v=-v;
    row[i]-=v;
    col[j]-=v;
    cell[i/3][j/3]-=v;
}

//得到最右边1的部分,如x=101100,lowbit(x)=100
int lowbit(int x){
    return x&-x;
}

//得到(x,y)位置的状态,即根据状态可以看出能放那几个数(1的位置)
int get(int x,int y){
    return row[x]&col[y]&cell[x/3][y/3];
}

//深度搜索
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=map[lowbit(i)];
        draw(x,y,t,true);
        if(dfs(cnt-1)) return true;
        draw(x,y,t,false);
    }
    return false;
}

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    //位数
    for(int i=0;i<N;i++) map[1<<i]=i;
    //状态一共有多少个1,即当前位置可以填多少个数
    //如100/000/100这个状态,说明(x,y)这个位置可以填2+1,8+1这两个数
    for(int i=0;i<1<<N;i++)8
        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);//填原状态,将row[i],col[j],cell[i/3][j/3]的t位置填起来
                }
                else cnt++;
                
        dfs(cnt);
        
        puts(str);
    }      

    return 0;
}

思路总结

  1. 初始化ones,map,状态数组row,col,cell
  2. 根据字符串,完成初始状态设置,draw(i,j,t,true)
  3. 进行搜索,选择分支数量最少的位置,即能填数最少的点。然后依次试验可不可行,注意恢复现场(get,lowbit)
  4. init,draw,get,lowbit,dfs

总结

  1. 每个位置能填的数,取决与横列和小宫格的状态&,是1即能填这个位置的数。初始化不填每个位置均为1。状态压缩
  2. dfs的优化搜索顺序非常的重要

木棒

原题链接

这里是引用

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef pair<int, int>pii;

const int N = 70;
int n;
int w[N];
int sum,length;
bool st[N];

//目前有u组,第u组当前长度为cur,然后该枚举第start根木棍
bool dfs(int u,int cur,int start){
    if(u*length==sum) return true;
    if(cur==length) return dfs(u+1,0,0);
    
    //排除等效冗余,start组合数
    for(int i=start;i<n;i++){
        //st标记数组非常重要,别忘了,可行性剪枝
        if(w[i]+cur>length||st[i]) continue;
        
        //选第i根木棍,标记不要忘了
        st[i]=true;
        if(dfs(u,w[i]+cur,i+1)) return true;
        st[i]=false;
        
        //排除有效冗余,当前长度为0 或者 选到刚好凑成一个length长度木棍失败,则肯定失败
        if(!cur||cur+w[i]==length) return false;
        //后面与这个长度相等的一样是不行,直接跳过
        int j=i;
        while(j<n&&w[j]==w[i]) j++;
        i=j-1;
    }
    return false;
}

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    while(cin>>n,n){
        memset(st,0,sizeof st);
        sum=0;
        for(int i=0;i<n;i++){
            cin>>w[i];
            sum+=w[i];
        }
        //优化搜索顺序
        sort(w,w+n);
        reverse(w,w+n);
        //迭代加深,搜索开始长度都为length时
        length=1;
        while(true){
            if(sum%length==0&&dfs(0,0,0)){
                cout<<length<<endl;
                break;
            }
            length++;
        }
    }

    return 0;
}

思路总结

  1. dfs当前正在凑第u根木棒,当前长度为cur,到第start根小木棍了
  2. 本题利用了优化搜索顺序,排除等效冗余,可行性剪枝,特别是等效冗余

总结

  1. 用组合数来排除等效冗余,加一个参数start
  2. 根据题意来排除等效冗余,第一个和最后一个能凑成length,不行直接失败
  3. 此类题目也非常的经典,每次从前往后扫描,凑成n个长度一样的组

生日蛋糕

原题链接

这里是引用

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<cstring>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 1e9
using namespace std;
typedef pair<int, int>pii;

const int N = 25;
int n,m;
int minv[N],mins[N];
int R[N],H[N];
int ans=inf;

//当前层数u为其选择合适的r和h,当前已有v,s。体积面积
void dfs(int u,int v,int s){
    if(v+minv[u]>n) return;
    if(s+mins[u]>=ans) return;
    //当n固定时,圆柱的表面积s=(2*v)/r,即当半径越大,表面积越小。高度越小
    if(s+2*sqrt(n-v)>=ans) return;
    if(s+2*(n-v)/R[u+1]>=ans) return;
    
    if(u==0){
        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--){
            int t=0;
            if(u==m) t=r*r;
            R[u]=r,H[u]=h;
            dfs(u-1,v+r*r*h,s+2*r*h+t);
        }
}

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>n>>m;
    //前i层最小体积和侧面积
    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) ans=0;
    cout<<ans<<endl;

    return 0;
}

思路总结

  1. 从下往上开始搜索合适的半径和高r,h(优化搜索顺序,从分支较少的开始搜索)
  2. 然后进行可行性,最优化剪枝,四种。r取最大,h取最小时,s最小
  3. 从大往小遍历选择这层(第u层)合适的r,h。确定赋值后进入上一层继续搜索。

总结

  1. 对于圆柱体积和表面积来说,如果体积固定,半径越大,高越小,表面积越小。V=rrh,S=2rh,S=2V/r,S=2sqrt(V*h)。即表面积与半径成反比,与高成“正比”。
  2. 每层选择合适的搜索,体会搜索的特点和本质。

这里是引用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值