编程语言
首页 > 编程语言> > 算法提高课 第二章 搜索之DFS

算法提高课 第二章 搜索之DFS

作者:互联网

一、DFS之连通性模型

1112. 迷宫

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

using namespace std;

const int N = 110;

int T,n;
char g[N][N];
int sx,sy,ex,ey;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
bool st[N][N],f;
void dfs(int x,int y)
{
    if(x == ex && y == ey)
    {
        f = 1;
        return;
    }
    st[x][y] = 1;
    for(int i = 0;i<4;i++)
    {
        int nx = x + dx[i],ny = y + dy[i];
        if(nx<0||nx>=n||ny<0||ny>=n||g[nx][ny] == '#'||st[nx][ny]) continue;
        dfs(nx,ny);
    }
}
int main()
{
    cin>>T;
    while(T--)
    {
        memset(st,0,sizeof st);
        cin>>n;
        for(int i = 0;i<n;i++)
        {
            for(int j = 0;j<n;j++)
            {
                cin>>g[i][j];
            }
        }
        cin>>sx>>sy>>ex>>ey;
        if(g[sx][sy] == '#'||g[ex][ey] == '#')
        {
            puts("NO");
            continue;
        }
        f = false;
        dfs(sx,sy);
        if(f) puts("YES");
        else puts("NO");
    }
    return 0;
}

1113. 红与黑

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

using namespace std;

const int N = 25;

char g[N][N];
int n,m,T;
bool st[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int ans;
void dfs(int x,int y)
{
    st[x][y] = 1;
    ++ans;
    for(int i = 0;i<4;i++)
    {
        int nx = x + dx[i],ny = y + dy[i];
        if(nx<1||nx>n||ny<1||ny>m||st[nx][ny]||g[nx][ny] == '#') continue;
        dfs(nx,ny);
    }
    return;
}
int main()
{
    while(cin>>m>>n,n||m)
    {
        ans = 0;
        memset(st, 0, sizeof st);
        int sx,sy;
        for (int i = 1; i <= n; i ++ )
        {
            for(int j = 1;j<=m;j++)
            {
                cin>>g[i][j];
                if(g[i][j] == '@')
                {
                    sx = i,sy = j;  
                }
            }
        }
        dfs(sx,sy);
        cout<<ans<<endl;
    }
    return 0;
}

1118. 分成互质组

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

using namespace std;

const int N = 15;

int group[N][N];//第i组第j个元素
int n,a[N];
int ans = 0x3f3f3f3f;
bool st[N];

int gcd(int a,int b)
{
    return b==0?a:gcd(b,a%b);
}

bool check(int g,int gc,int x) //判断x是否与g组所有元素互质
{
    for(int i = 0;i<gc;i++)
    {
        if(gcd(group[g][i],x)>1) return false;
    }
    return true;
}
void dfs(int g,int gc,int u,int start) //g:第几组,gc:组里有多少个数 u:当前搜到了第几个元素 start:搜索起始点
{
    if(g >= ans) return;//当前组数超过了答案,一定不可行,剪枝
    if(u == n) //搜完了所有元素,求出组数最小值
    {
        ans = min(ans,g);
        return;
    }
    bool f = false; //当前组是否添加进新元素
    for(int i = start;i<n;i++)
    {
        if(!st[i] && check(g,gc,a[i]))//没有添加过且与组里元素都互质
        {
            group[g][gc] = a[i];//添加进组
            st[i] = true;
            dfs(g,gc+1,u+1,start+1);//搜索下一个元素
            f = true;//修改已添加
            
            st[i] = false;//恢复现场

        }
    }
    if(!f) //当前组无法添加元素,新开一个组,从头开始搜
    {
        dfs(g+1,0,u,0);
    }
}
int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
    {
        scanf("%d", &a[i]);
    }
    dfs(1,0,0,0);
    cout<<ans<<endl;
    return 0;
}

二、DFS之搜索顺序

1116. 马走日

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

using namespace std;
typedef long long LL;

const int N = 10;

int T,n,m,x,y;
LL ans;
int dx[] = {-1,-2,-2,-1,1,2,2,1},dy[] = {-2,-1,1,2,2,1,-1,-2};
bool st[N][N];
void dfs(int x,int y,int cnt)
{
    if(cnt == n*m) //注意:搜到第n*m个点时就可以返回了
    {
        ++ans;
        return;
    }
    st[x][y] = 1;
    for(int i = 0;i<8;i++)
    {
        int nx = x + dx[i],ny = y + dy[i];
        if(nx<0||nx>=n||ny<0||ny>=m||st[nx][ny]) continue;
        dfs(nx,ny,cnt+1);
    }
    st[x][y] = 0;//注意:恢复现场写在外面
}
int main()
{
    cin>>T;
    while(T--)
    {
        ans = 0;
        memset(st,0,sizeof st);
        cin>>n>>m>>x>>y;
        dfs(x,y,1);
        cout<<ans<<endl;
    }
    return 0;
}

1117. 单词接龙

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

using namespace std;

const int N = 25;

string word[N];
int n;
int ans;
int st[N];

void dfs(string cur,int u) //理解dfs函数形参结构,cur为当前拼接字符串,u为最后一次加入的字符串
{
    //cout<<cur<<endl;
    ans = max(ans,(int)cur.size());
    ++st[u];//不能写进循环体内
    for(int i = 0;i<n;i++) //枚举所有字符串
    {
        if(st[i] == 2) continue; //注意次数限制
        
            for(int j = 1;j<min(cur.size(),word[i].size());j++)  
            {
                if(cur.substr(cur.size()-j) == word[i].substr(0,j)) //能拼接则进行拼接
                {
                     dfs(cur + word[i].substr(j),i);
                }
            }
        
    }
    --st[u];//不能写进循环体内
}
int main()
{
    cin>>n;
    for(int i = 0;i<n;i++)
    {
        cin>>word[i];
    }
    char c;
    cin>>c;
    for(int i = 0;i<n;i++)
    {
        if(word[i][0] == c)
        {
            dfs(word[i],i);
        }
    }
    cout<<ans<<endl;
    return 0;
}

1118. 分成互质组

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

using namespace std;

const int N = 15;

int group[N][N];//第i组第j个元素
int n,a[N];
int ans = 0x3f3f3f3f;
bool st[N];

int gcd(int a,int b)
{
    return b==0?a:gcd(b,a%b);
}

bool check(int g,int gc,int x) //判断x是否与g组所有元素互质
{
    for(int i = 0;i<gc;i++)
    {
        if(gcd(group[g][i],x)>1) return false;
    }
    return true;
}
void dfs(int g,int gc,int u,int start) //g:第几组,gc:组里有多少个数 u:当前搜到了第几个元素 start:搜索起始点
{
    if(g >= ans) return;//当前组数超过了答案,一定不可行,剪枝
    if(u == n) //搜完了所有元素,求出组数最小值
    {
        ans = min(ans,g);
        return;
    }
    bool f = false; //当前组是否添加进新元素
    for(int i = start;i<n;i++)
    {
        if(!st[i] && check(g,gc,a[i]))//没有添加过且与组里元素都互质
        {
            group[g][gc] = a[i];//添加进组
            st[i] = true;
            dfs(g,gc+1,u+1,start+1);//搜索下一个元素
            f = true;//修改已添加
            
            st[i] = false;//恢复现场

        }
    }
    if(!f) //当前组无法添加元素,新开一个组,从头开始搜
    {
        dfs(g+1,0,u,0);
    }
}
int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ )
    {
        scanf("%d", &a[i]);
    }
    dfs(1,0,0,0);
    cout<<ans<<endl;
    return 0;
}

三、DFS之剪枝

image

165. 小猫爬山

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

using namespace std;

const int N = 20;

int sum[N],c[N],n,w;
int ans;

void dfs(int cnt,int u) //cnt表示当前用了几辆车,u表示枚举到第几只猫
{
    if(cnt >= ans) return;
    if(u == n+1)
    {
        ans = min(ans,cnt);
        return;
    }
    
    for(int i = 0;i<cnt;i++) //枚举所有车,能放第u只猫则放
    {
        if(sum[i] + c[u] > w) continue; //可行性剪枝
        sum[i] += c[u]; //加入第i辆车
        dfs(cnt,u+1); //为下一只猫找车
        sum[i] -= c[u]; //注意:恢复现场
    }
    
    sum[cnt] = c[u]; //新开一辆车,放第u只猫
    dfs(cnt+1,u+1); //枚举下一只猫
    sum[cnt] = 0; //恢复现场
    return;
}
bool cmp(int a,int b)
{
    return a>b; //必须是>号
}

int main()
{
    cin>>n>>w;
    for(int i = 1;i<=n;i++)
    {
        cin>>c[i];
    }
    ans = n;
    sort(c+1,c+n+1,cmp); //降序排序,减少搜索层数
    dfs(0,1);
    cout<<ans<<endl;
    return 0;
}

数独

image

优化搜索顺序:优先搜索分支较小的,即格子中可能的数较少的
可行性剪枝:所在行、所在列以及所在九宫格中没有重复数字
位运算优化:用九位二进制数表示1~9是否可用,行、列及九宫格三个值进行与运算,lowbit取出所有的1,减少循环次数

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

using namespace std;

const int N = 9,M = 1<<N;

int ones[M];//预处理:一个状态里面有多少个1
int map[M]; //预处理:lowbit返回的是2的次幂,因此需要求出2的几次幂 map(x) = log2(x)
int row[N],col[N],cell[3][3]; //每一行、每一列、每个九宫格能放的数字的二进制表示

char str[100];

int lowbit(int x)
{
    return x & -x;
}
int get(int x,int y) //返回当前位置能填的数字方案的二进制表示
{
    return row[x] & col[y] & cell[x/3][y/3];
}
void init() //初始化:将所有行、列、九宫格的状态设置为1~9可填
{
    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;
        }
    }
}
void draw(int x,int y,int t,bool f) //在位置(x,y)上填数字t(f = 1) or 删去t(f = 0) 
{
    //对字符串进行实际的填入或删除操作
    if(f) str[x*N + y] = t + '1'; 
    else str[x*N + y] = '.';
    
    //更新对应行、列、九宫格的状态
    int v = 1<<t;
    if(f)
    {
        row[x] -= v;
        col[y] -= v;
        cell[x/3][y/3] -= v;
    }
    else
    {
        row[x] += v;
        col[y] += v;
        cell[x/3][y/3] += v;
    }
}
bool dfs(int cnt)
{
    if(cnt == 0) return true;
    
    //优化操作:找到可填数字最少的格子(分支数最少)
    int minv = 10;
    int minx,miny;
    int k = 0;
    for(int x = 0;x<N;x++)
    {
        for(int y = 0;y<N;y++,k++)
        {
            if(str[k] == '.') //为空
            {
                int state = get(x,y); //得到方案的二进制表示
                if(ones[state] < minv) //方案中几个1表示有几个数字可填
                {
                    minv = ones[state];
                    minx = x,miny = y;    
                }
            }
        }
    }
    
    //从分支最少的方案开始搜
    int state = get(minx,miny);
    for(int i = state;i;i -= lowbit(i)) //枚举状态中所有1
    {
        int t = map[lowbit(i)]; //取出1所表示的能放的数字
        draw(minx,miny,t,1); //放入该数字
        if(dfs(cnt-1)) return true; //搜索下一个位置,若成功放置,返回true
        draw(minx,miny,t,0);//恢复现场
    }
    
    return false; //找不到任何方案,返回false
}
int main()
{
    for(int i = 0;i<N;i++) //预处理:lowbit返回的是2的次幂,因此需要求出2的几次幂 map(x) = log2(x)
    {
        map[1<<i] = i;
    }
    for(int i = 0;i<M;i++) //预处理:一个状态里面有多少个1
    {
        for(int j = 0;j<N;j++)
        {
            ones[i] += i >> j & 1;
        }
    }
    
    while(cin>>str,str[0] != 'e')
    {
        init();//初始化
        
        int cnt = 0; //空格子的个数
        int k = 0;//枚举所有格子
        for(int x = 0;x<N;x++)//x,y表示位置
        {
            for(int y = 0;y<N;y++,k++) 
            {
                if(str[k] != '.')
                {
                    int t = str[k] - '1'; //注意:减去'1'
                    draw(x,y,t,1); //根据给定格子的数更新状态
                }
                else ++cnt;
            }
        }
        
        dfs(cnt);
        puts(str);
    }
    return 0;
}

木棒

image

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

using namespace std;

const int N = 70;
int sum,n,Max;
int a[N];
int len; 
bool st[N]; //第i根小棒是否被使用

bool dfs(int cnt,int start,int cur) //cnt:当前组装的长度为len的大棒个数,start为枚举起点
{
    if(cnt*len == sum) return true; //搜索到结果
    if(cur == len) return dfs(cnt+1,0,0);//当前大棒已组成,组装下一根
    
    //剪枝1:排除等效冗余,从start开始枚举
    for(int i = start;i<n;i++)
    {
        if(st[i]) continue;//使用过的不用
        if(cur + a[i] > len) continue; //剪枝2:可行性剪枝,超长度的不用
        st[i] = 1; //标记已使用
        if(dfs(cnt,i+1,cur + a[i])) return true; //若能组装
        
        //若不能组装
        st[i] = 0;//恢复现场
        if(cur == 0) return false; //剪枝3:可行性剪枝,当前第一根小棒都加入失败,则一定失败
        if(cur + a[i] == len) return false; //剪枝4:可行性剪枝,当前最后一根不同长度的小棒加入失败,则一定失败
        
        int j;
        for(j = i;j<n && a[j] == a[i];j++);//剪枝5:可行性剪枝,当前长度小棒加入失败,则后面相同的也一定失败
        i = j - 1;
    }
    return false;
}
int main()
{
    while(cin>>n,n)
    {
        memset(st, 0, sizeof st);
        sum = 0,Max = 0;
        
        for(int i = 0;i<n;i++)
        {
            cin>>a[i];
            Max = max(Max,a[i]);//每个相同小棒的长度必须大于给定小棒的最大值
            sum += a[i];
        }
        sort(a,a+n);//优化搜索顺序:降序排序,减少搜索分支
        reverse(a,a+n);
        for(len = Max;;len++)//优化搜索顺序:从最大值开始枚举
        {
            if(sum % len == 0 && dfs(0,0,0))
            {
                cout<<len<<endl;
                break;
            }
        }
    }
    return 0;
}

168. 生日蛋糕

image

标签:第二章,return,int,DFS,st,算法,dfs,ans,include
来源: https://www.cnblogs.com/zjq182/p/16383611.html