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

cpp 复制代码
#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,才能放进去.

cpp 复制代码
#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存储下一给木棍从哪里开始; 如果当前木棍放到当前棒子中石板路,则直接掠过其他所有长度相等的木棍;如果是当前木棒的第一个木棍失败,则一定失败;如果放到最后一个失败,则也一定失败

cpp 复制代码
#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

cpp 复制代码
#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;
}
相关推荐
山顶夕景3 小时前
【Leetcode152】分割回文串(回溯 | 递归)
算法·深度优先·回溯
WenGyyyL8 小时前
力扣最热一百题——二叉树的直径
java·c++·算法·二叉树·深度优先
人才程序员21 小时前
CSP-J 算法基础 广度优先搜索BFS
数据结构·c++·算法·深度优先·宽度优先·比赛·noi
arin8763 天前
【组合】矩阵ksm+状压dp
算法·矩阵·深度优先
EllinY3 天前
CF 231 E Cactus 题解(仙人掌图上找环)
c++·笔记·算法·深度优先·图论
joker_zh533 天前
846. 树的重心
算法·深度优先·树的重心
我明天再来学Web渗透4 天前
【hot100-java】【对称二叉树】
java·开发语言·数据结构·算法·leetcode·链表·深度优先
MIMO. mimo8 天前
深度优先算法,广度优先算法,hill climbing,贪心搜索,A*算法,启发式搜索算法是什么,比起一般搜索法算法有什么区别
算法·深度优先·宽度优先
逝去的秋风8 天前
【代码随想录训练营第42期 续Day52打卡 - 图论Part3 - 卡码网 103. 水流问题 104. 建造最大岛屿
算法·深度优先·图论