DFS之剪枝与优化

DFS之剪枝与优化

dfs和bfs都是对应一个搜索树,常用的剪枝策略:
①优化搜索顺序:大部分情况下,我们应该优先搜索分支较少的节点。
②排除等效冗余
③可行性剪枝
④最优性剪枝
⑤记忆化搜索(dp)

165. 小猫爬山

翰翰和达达饲养了 N只小猫,这天,小猫们要去爬山。
经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。
翰翰和达达只好花钱让它们坐索道下山。
索道上的缆车最大承重量为 W,而 N只小猫的重量分别是 C1、C2……CN。
当然,每辆缆车上的小猫的重量之和不能超过 W。
每租用一辆缆车,翰翰和达达就要付 1美元,所以他们想知道,最少需要付多少美元才能把这 N只小猫都运送下山?

输入格式
第 1行:包含两个用空格隔开的整数,N和 W。
第 2…N+1行:每行一个整数,其中第 i+1行的整数表示第 i只小猫的重量 Ci。

输出格式
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。

数据范围
1≤N≤18,
1≤Ci≤W≤10^8
输入样例:
5 1996
1
2
1994
12
29
输出样例:
2

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 20;

int n,m;
int w[N];
int sum[N];
int ans=N;

void dfs(int u,int k){
    //最优性剪枝
    if(k>=ans) return ;
    if(u==n){
        ans=k;
        return;
    }
    for(int i=0;i<k;i++){
        if(sum[i]+w[u]<=m){//可行性剪枝
            sum[i]+=w[u];
            dfs(u+1,k);
            sum[i]-=w[u];//恢复现场
        }
    }
    sum[k]=w[u];
    dfs(u+1,k+1);
    sum[k]=0;//恢复现场
}

int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++) cin>>w[i];
    //优化搜索顺序
    sort(w,w+n);
    reverse(w,w+n);
    dfs(0,0);//从零号猫,当前车的数量是0开始搜索
    cout<<ans<<endl;
    return 0;
}

166. 数独

数独 是一种传统益智游戏,你需要把一个 9×9的数独补充完整,使得数独中每行、每列、每个 3×3的九宫格内数字 1∼9均恰好出现一次。
请编写一个程序填写数独。
输入格式
输入包含多组测试用例。
每个测试用例占一行,包含 81个字符,代表数独的 81个格内数据(顺序总体由上到下,同行由左到右)。
每个字符都是一个数字(1−9)或一个 .(表示尚未填充)。
您可以假设输入中的每个谜题都只有一个解决方案。
文件结尾处为包含单词 end 的单行,表示输入结束。
输出格式
每个测试用例,输出一行数据,代表填充完全后的数独。
输入样例:
4…8.5.3…7…2…6…8.4…1…6.3.7.5…2…1.4…
…52…8.4…3…9…5.1…6…2…7…3…6…1…7.4…3.
end
输出样例:
417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936
解题思路
位运算优化:行,列,九宫格,都可以用9个二进制数表示(1表示可放,0表示不能放),只有三者取&后,如果仍为1,才能放进去.

#include<iostream>
#include<cstring>
#include<algorithm>

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(){
    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)//is_set,可以填,就是dfs的过程,反之则是恢复现场
{
    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;
    col[y]-=v;
    cell[x/3][y/3]-=v;
}

int lowbit(int x)
{
    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;
    //找个分支最小的进行遍历
    
    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;
}

int main(){
    for(int i=0;i<N;i++) map[1<<i] = i;
    for(int i=0;i< 1<<N;i++){//每个二进制数里面有多少个1
        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);
                 }
                 else cnt++;
             }
        }
        dfs(cnt);
        puts(str);
    }
    return 0;
}

167. 木棒

乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50个长度单位。
然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。
请你设计一个程序,帮助乔治计算木棒的可能最小长度。
每一节木棍的长度都用大于零的整数表示。
输入格式
输入包含多组数据,每组数据包括两行。
第一行是一个不超过 64的整数,表示砍断之后共有多少节木棍。
第二行是截断以后,所得到的各节木棍的长度。
在最后一组数据之后,是一个零。
输出格式
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。
数据范围
数据保证每一节木棍的长度均不大于 50。
输入样例:
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出样例:
6
5
解题思路
① length | sum
②优先搜索顺序,从大到小枚举
③排除等效冗余,按照组合数的方式枚举,start存储下一给木棍从哪里开始; 如果当前木棍放到当前棒子中石板路,则直接掠过其他所有长度相等的木棍;如果是当前木棒的第一个木棍失败,则一定失败;如果放到最后一个失败,则也一定失败

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 70;

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

bool dfs(int u,int s,int start)//u代表当前的木棍的编号,s表示当前大棍的长度
{
    if(u*length==sum) return true;
    if(s==length) return dfs(u+1,0,0);//走下一步的搜索
    for(int i=start;i<n;i++){
        if(st[i]) continue;//用过了
        if(s+w[i]>length) 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]==length) 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];
        //优化搜索顺序
        sort(w,w+n);
        reverse(w,w+n);
        length=1;
        while(1){
            //剪枝1
            if(sum%length==0&&dfs(0,0,0)){
                cout<<length<<endl;
                break;
            }
            length++;
        }
    }
    return 0;
}

168. 生日蛋糕

7月 17日是 Mr.W 的生日,ACM-THU 为此要制作一个体积为 Nπ的 M层生日蛋糕,每层都是一个圆柱体。
设从下往上数第 i层蛋糕是半径为 Ri,高度为 Hi的圆柱。
当 i<M时,要求 Ri>Ri+1且 Hi>Hi+1。
由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积 Q最小。
令 Q=Sπ ,请编程对给出的 N和 M,找出蛋糕的制作方案(适当的 Ri
和 Hi的值),使 S最小。
除 Q外,以上所有数据皆为正整数。

输入格式
输入包含两行,第一行为整数 N,表示待制作的蛋糕的体积为 Nπ。
第二行为整数 M,表示蛋糕的层数为 M。

输出格式
输出仅一行,是一个正整数 S(若无解则 S=0)。

数据范围
1≤N≤10000,
1≤M≤20
输入样例:
100
2
输出样例:
68

#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

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 -- )
        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);
        }
}

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) ans = 0;
    cout << ans << endl;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值