【蓝桥杯】dfs专题


DFS专题

题库:在线题库_在线做题_智能评测_蓝桥云课题库 - 蓝桥云课 (lanqiao.cn)

1.小朋友崇拜圈(拓扑排序 + dfs图的遍历)

题意:找最大的圈(环),输出结果。

思路:一个环中每一个点的入度至少为1,那么入度为0的点不可能构成环,因此拓扑排序将入度为1的点去掉,然后dfs遍历得到每一个环的大小,去最值即可。

【代码实现】

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


using namespace std;

const int N = 1e5 + 10, M = 2 * N;
int h[N], e[M], ne[M], idx;
int d[N];// 统计入度情况
bool st[N];
int a[N];

int n;

void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}


void topsort()
{
    queue<int> q;
    for (int i = 1; i <= n; i ++ )
        if(d[a[i]] == 0)
            q.push(i);
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            d[j] --;
            if(d[j] == 0)
                q.push(j);
        }
    }
}

// int dfs(int u, int father, int sum)
// {
//     if(st[u]) return cnt;
//     st[u] = true;
    
//     sum ++;
//     for(int i = h[u]; i != -1; i = ne[i])
//     {
//         int j = e[i];
//         if(j == father) continue;
//         return dfs(j, u, sum);
//     }
// }

// 下面这个dfs函数在acwing能过,蓝桥杯官网报的编译错误(我不理解)
int dfs(int u, int father)
{
    
    st[u] = true;

    int sum = 1;
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(!st[j])
        {
            int s = dfs(j, u);
            sum += s;
        }
    }
    return sum;
}

int main()
{
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ )
    {
        cin >> a[i];
        d[a[i]] ++;
        add(i, a[i]);
    }
    topsort();
    
    int ans = -1e8;
    for (int i = 1; i <= n; i ++ )
    {
        if(d[i] && !st[i])// 度不为0且没被访问过
        {
            int tmp = dfs(i, -1);
            // cout << tmp << endl;
            ans = max(ans, tmp);
        }
            
    }
    // puts("");
    cout << ans;
    
    return 0;
}

2.路径之迷(dfs + 路径打印)

【题目链接】https://www.lanqiao.cn/problems/89/learning/

搜索每一个由起点走到终点的所有路线,经过每一个位置时,给它的x、y都加上1的贡献,最后到达右下角时,如果x、y的值等于输入的箭靶的数目则为正确答案

【代码实现】

只得了一部分分数(40%)

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


using namespace std;

const int N = 25;
int g[N][N];
int rx[N], ry[N];
int nx[N], ny[N];
vector<int> path;
bool st[N][N];
bool is_find;
int n;

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};


bool check()
{
    for (int i = 0; i < n; i ++ ) 
        if(rx[i] != nx[i] || ry[i] != ny[i]) 
            return false;
    return true;    
}

void dfs(int x, int y)
{
    if(is_find) return ;// 结束递归
    if(x == n - 1 && y == n - 1 && check())
    {
        // if(check())
        // {
            is_find = true;
            for (int i = 0; i < path.size(); i ++) cout << path[i] << ' ';
        // }    
        return ;    
    }
    for(int i = 0; i < 4; i ++)
    {
        int a = x + dx[i], b = y + dy[i];
        if(a < 0 || a >= n || b < 0 || b >= n) continue;
        if(st[a][b]) continue;
        
        st[a][b] = true;
        rx[a] ++;
        ry[b] ++;
        path.push_back(g[a][b]);
        
        dfs(a, b);
        
        //回溯: 恢复现场
        st[a][b] = false;
        rx[a] --;
        ry[b] --;
        path.pop_back();
        
    }
    
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> ny[i];
    for (int i = 0; i < n; i ++ ) cin >> nx[i];
    
    int x = 0;
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < n; j ++ )
            {
                g[i][j] = x ++;
                // cout << g[i][j] << ' ';
            }
    }        
    st[0][0] = true;
    rx[0] ++;
    ry[0] ++;
    path.push_back(0);
    dfs(0, 0);
    return 0;
}

【代码二】

(70%)

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

#define x first
#define y second


using namespace std;

const int N = 25;
typedef pair<int, int> PII;

int nx[N], ny[N];
vector<PII> path;
bool st[N][N];
bool flag;
int n;

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};


bool check()
{
    for (int i = 0; i < n; i ++ ) 
        if(nx[i] || ny[i]) 
            return false;
    return true;    
}

void dfs(int x, int y)
{
    st[x][y] = true;
    nx[x] --, ny[y]--;
    path.push_back({x, y});
    
    // 剪枝
    if(nx[x] < 0 || ny[x] < 0) return ;
    
    if(x == n - 1 && y == n - 1)
    {
        if(check()) flag = true;
    	// 一维坐标和二维坐标的转换关系
        if(flag)
        for (int i = 0; i < path.size(); i ++) cout << path[i].x * n + path[i].y << ' ';
        exit(0);// 直接结束程序
        return ;    
    }
    for(int i = 0; i < 4; i ++)
    {
        int a = x + dx[i], b = y + dy[i];
        if(a < 0 || a >= n || b < 0 || b >= n) continue;
        if(st[a][b]) continue;
    
        dfs(a, b);
        
        //回溯: 恢复现场
        st[a][b] = false;
        nx[a] ++;
        ny[b] ++;
        path.pop_back();
        
    }
    
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> ny[i];
    for (int i = 0; i < n; i ++ ) cin >> nx[i];
    
    dfs(0, 0);
    return 0;
}

3.网络寻路(dfs 树/图的遍历)

【题目链接】网络寻路 - 蓝桥云课 (lanqiao.cn)

记录一下前一个状态,每次搜索的节点不能跟前一个节点相等,被转发两次即为一条可行方案。

【代码实现】

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

#define x first
#define y second


using namespace std;

const int N = 1e5 + 10, M = 2 * N;
int h[N], e[M], ne[M], idx;
bool st[N];
int n, m;
int ans, start;

void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u, int k, int father)
{
    
    if(k == 4)// 被转发两次
    {
        ans ++;
        return ;
    }
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == father) continue;
        dfs(j, k + 1, u);    
    }
}

// void dfs(int u, int k, int father)
// {
    
//     if(k == 4)// 被转发两次
//     {
//         ans ++;
//         return ;
//     }
//     for(int i = h[u]; i != -1; i = ne[i])
//     {
//         int j = e[i];
//         if(j == father) continue;
//         if(!st[j])
//         {
//             st[j] = true;// 若果做标记的话就要记得回溯!!!
//             dfs(j, k + 1, u);
//             st[j] = false;
//         }        
//     }

// }


int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    
    int res = 0;
    for (int i = 1; i <= n; i ++ )
    {
        memset(st, 0, sizeof st);
        dfs(i, 1, -1);
    }
    cout << ans;
    return 0;
}

4.危险系数(dfs 树/图的遍历)

【题目链接】危险系数 - 蓝桥云课 (lanqiao.cn)

思路:

搜索所有由起点到终点的路径,记录点的个数,若果某一个点的个数 = 路径数,说明该点为必经过点(关键点)
如果起点终点不连通直接输出-1

【代码实现】

#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>

#define x first
#define y second


using namespace std;

const int N = 1e5 + 10, M = 2 * N;
int h[N], e[M], ne[M], idx;
bool st[N];
int cnt[N];
int n, m;
int u, v;
int ans;
bool flag;

void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

// 搜索所有由起点到终点的路径,记录点的个数,若果某一个点的个数 = 路径数,说明该点为必经过点(关键点)
// 如果起点终点不连通直接输出-1

void dfs(int u, int father)
{
    if(u == v)
    {
        flag = true;
        ans ++;
        for (int i = 1; i <= n; i ++ )
            if(st[i])
                cnt[i] ++;// 记录路径点出现的次数
        return ;        
    }
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(j == father) continue;
        if(!st[j])
        {
            st[j] = true;
            dfs(j, u);
            st[j] = false;// 回溯
        }
    }
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    
    cin >> u >> v;
    st[u] = true;
    dfs(u, -1);
    
    int res = 0;
    for (int i = 1; i <= n; i ++ )
        if(ans == cnt[i]) res ++;
   
    if(!flag) puts("-1");
    else cout << res - 2;// 减去起点和终点    
    

    return 0;
}

5.发现环(并查集判环+dfs图的遍历)

【题目链接】发现环 - 蓝桥云课 (lanqiao.cn)

内容重复请跳转链接:【蓝桥杯】并查集专题_塔塔开!!!的博客-CSDN博客

6.七段码(建图+并查集 + dfs(指数类型枚举))

【题目链接】七段码 - 蓝桥云课 (lanqiao.cn)

内容重复请跳转链接:【蓝桥杯】并查集专题_塔塔开!!!的博客-CSDN博客

7.正则问题(dfs 二叉树)

【题目链接】

二叉树跟递归搜索树,当遇到|时,字符串长度为左右两边取最大值,遇到x答案就加一,关键点:遇到左括号时必然会遇到右括号

【代码实现】

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

using namespace std;

string str;
int k;

int dfs()// 返回x的个数
{
    int res = 0;
    while(k < str.size())
    {
        if(str[k] == '(')// 处理(.....)
        {
            k ++;// 跳过(
            res += dfs();// 计算(里边的x
            k ++;//跳过 )括号
        }
        else if(str[k] == '|')
        {
            k ++;// 跳过)
            res = max(res, dfs());
        }
        else if(str[k] == ')') break;
        else// 如果是x,跳过
        {
            k ++;
            res ++;
        }
    }
    return res;
}

int main()
{
    cin >> str;
    cout << dfs() << endl;
    return 0;
}

代码2:

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

using namespace std;

string str;
int k;

int dfs()// 返回x的个数
{
    int res = 0;
    while(k < str.size())
    {
        if(str[k] == '(')// 处理(.....)
        {
            k ++;// 跳过(
            res += dfs();// 计算(里边的x
           
        }
        else if(str[k] == '|')
        {
            k ++;// 跳过)
            res = max(res, dfs());
            return res;// 返回大的一边
        }
        else if(str[k] == ')')
        {
             k ++;//跳过)括号
             return res;
        }
        else// 如果是x,跳过
        {
            k ++;
            res ++;
        }
    }
    return res;
}

int main()
{
    cin >> str;
    cout << dfs() << endl;
    return 0;
}

8.N皇后问题(dfs 八皇后问题)

【题目链接】N皇后问题 - 蓝桥云课 (lanqiao.cn)

八皇后问题

【代码实现】

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

using namespace std;

const int N = 25;

bool col[N], dg[N], udg[N];
int n;
int ans;

void dfs(int u)
{
    if(u == n)
    {
        ans ++;
        return ;
    }
    for(int j = 0; j < n; j ++)
    {
        if(!col[j] && !dg[j - u + n] && !udg[j + u])
        {
            col[j] = dg[j - u + n] = udg[j + u] = true;
            dfs(u + 1);
            col[j] = dg[j - u + n] = udg[j + u] = false;// 回溯
        }
    }
}

int main()
{
    cin >> n;
    dfs(0);
    cout << ans;
    return 0;
}

9.剪格子

【题目链接】剪格子 - 蓝桥云课 (lanqiao.cn)

因为要把m x n的格子分割为两个部分,使得这两个区域的数字和相等,所以两个区域的数字和等于总的数字和除以2。

说明:
总的数字和有两种情况,一是奇数,二是偶数
①奇数时,比如5,5 ÷ 2 = 2.5,因为格子都是整数,所以找不到,输出0
②偶数时,可以找到。

【代码实现】

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

using namespace std;

typedef long long LL;
const int N = 15, INF = 0x3f3f3f3f;
int g[N][N];
bool st[N][N];
int n, m;
int ans = INF;
LL target;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

// u:走到的格子数
void dfs(int u, int x, int y, LL sum)
{

    // 递归出口(结束条件)
    if(sum == target) 
    {
        ans = min(ans, u);
        return ;
    }
    // 剪枝:z
 	if (sum > target || u > ans)
 		return;    
		
    for(int i = 0; i < 4; i ++)
    {
        int a = x + dx[i], b = y + dy[i];
        if(a < 0 || a >= n || b < 0 || b >= m) continue;
        if(st[a][b]) continue;
        
        st[a][b] = true;
        dfs(u + 1, a, b, sum + g[a][b]);
        st[a][b] = false;
    }
}

int main()
{
    // 先输入列号 再输入行号!!!!!!
    cin >> m >> n;
    
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
        {
            cin >> g[i][j];
            target += g[i][j];
        }    
        
    target /= 2;        
    st[0][0] = true;        
    dfs(1, 0, 0, g[0][0]);

    
    if(ans != INF) cout << ans;
    else cout << 0;
    
    return 0;
}

10.盾神与砝码称重

【题目链接】蓝桥杯算法提高VIP-盾神与砝码称重 - C语言网 (dotcpp.com)

dfs深搜:

有三种选择:

  1. 砝码放左边
  2. 砝码放右边(与物品一起)
  3. 不放

【代码实现】

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

using namespace std;

const int N = 30;
int v[N], w[N];
int n, m;
bool flag;

// 三种选择
// 1.放左边
// 2.放右边
// 不妨
void dfs(int u, int l, int r)
{
    if(u == n + 1)
    {
        if(l == r) flag = true;
        return ;
    }
    dfs(u + 1, l + v[u], r);
    dfs(u + 1, l , r + v[u]);
    dfs(u + 1, l, r);
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i];
    for (int i = 1; i <= m; i ++ ) cin >> w[i];
    
    for (int i = 1; i <= m; i ++ )
    {
        flag = false;
        dfs(1, 0, w[i]);
        if(flag) puts("YES");
        else puts("NO");
    }
    
    return 0;
}

01背包求解:

【代码实现】

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

using namespace std;

const int N = 1e5 + 10;
int v[N], w[N];
bool f[N];
// f[i]:体积为i的物品能否被凑出来
// 从前i个物品中选,积极恰好为j的所有方案的集合
// 属性:bool
int n, m;

// 这样不能通过全部的数据,因为没有考虑到第三种情况。
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> v[i];
    for (int i = 1; i <= m; i ++ ) cin >> w[i];
    
    for (int k = 1; k <= m; k ++ )
    {
        memset(f, 0, sizeof f);
        f[0] = true;
        for (int i = 1; i <= n; i ++ )
            for(int j = w[k]; j >= 0; j --)
            {
                f[j] |= f[j - v[i]];
            }
            
        if(f[w[k]]) puts("YES");
        else puts("NO");
    }        
   
    
    return 0;
}

11.砝码称重

【题目链接】3417. 砝码称重 - AcWing题库

在这里插入图片描述

【题目链接】

三种选择方式:

1.不选第i个物品

2.第i个物品放左边(负)

3.第i个物品放右边(正)

状态表示:f[i][j]:从前i个物品中选,且重量为j的所有方案的集合

属性:bool:是否非空

初始化:f[0][0] = true

在这里插入图片描述

【代码实现】

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

using namespace std;


const int N = 110, M = 2e5 + 10, B = M / 2;// 偏移量

int n, m;
int w[N];
bool f[N][M];

int main()
{
    cin >> n;
    
    for (int i = 1; i <= n; i ++ ) 
    {
        cin >> w[i];
        m += w[i]; // 总重量
    }
    
    f[0][0 + B] = true;// 初始化
    for (int i = 1; i <= n; i ++ )
        for(int j = -m; j <= m; j ++)// 枚举重量:范围-m ~ m(-m可能很小,为保证下标不为负数 要加偏移量)
        {
            f[i][j + B] = f[i - 1][j + B];
            if(j + w[i] <= m) f[i][j + B] |= f[i - 1][j + w[i] + B];
            if(j - w[i] >= -m) f[i][j + B] |= f[i - 1][j - w[i] + B];
        }
    int ans = 0;
    for (int j = 1; j <= m; j ++ )
        if(f[n][j + B])
            ans ++;
    cout << ans;        
    return 0;
}

dfs暴力枚举

一半的分数:

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

using namespace std;

const int N = 1e5 + 10;
bool st[N];// 用于记录该重量是否出现过了,避免重复出现
int w[N];
int n, ans;

void dfs(int u, int sum)
{
    if(u == n + 1)
    {
        if(sum > 0 && !st[sum])
        {
            st[sum] = true;
            ans ++;
        }
        return ;
    }
    dfs(u + 1, sum + w[u]);
    dfs(u + 1, sum - w[u]);
    dfs(u + 1, sum);

}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> w[i];
    dfs(1, 0);
    cout << ans;
    return 0;
}
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值