当前位置: 首页 > news >正文

【蓝桥杯】每日练习 Day15

目录

前言

奶牛选美

分析

代码

大臣的旅费

分析

代码

飞机降落

分析

代码

母亲的牛奶

分析

代码

扫雷

分析

代码


前言

虽为诞辰,但也不忘完成每日的训练。

今天给大家带来五道dfs的题目,包括组合数,连通块,数的直径等方面的内容。

因为时间较为仓促,很多地方可能讲的不是很全面,请见谅。


奶牛选美

分析

题目保证图中存在且仅存在两个连通块,说实话这道题挺水的,不知道为什么难度是中等。

第一步,dfsbfs找出两个连通块。

随后的问题就是求两个连通块的最小曼哈顿距离,枚举两个连通块中的点,随后取最小值即可(这题题目的结果要减一)。

abs(x1 - x2) + abs(y1 - y2)

最后我们来分析一下时间复杂度,dfs或bfs的时间复杂度都是O(n ^ 2),求曼哈顿距离的时间复杂度是O(x * (n - x))x表示第一个连通块中的连通块数量),可以发现这是一个基本不等式,最大值为(n ^ 2) / 4,所以总的时间复杂度就是O(n ^ 2)。通过本题绰绰有余。


代码

// dfs连通块
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
#define s second
#define f first
using namespace std;
typedef pair<int, int> PII;
const int N = 55;
int n, m;
char map[N][N];
bool read[N][N];
vector<PII> v[3];
int cnt;
int l = 0x3f3f3f3f;
PII s[] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

void dfs(int x, int y, vector<PII>& v)
{
    read[x][y] = true;
    v.push_back({x, y});
    for(int i = 0; i < 4; i++)
        if(map[x + s[i].f][y + s[i].s] == 'X' && !read[x + s[i].f][y + s[i].s])
            dfs(x + s[i].f, y + s[i].s, v);
}


int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        scanf("%s", map[i] + 1);
    
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            if(map[i][j] == 'X' && !read[i][j])
                dfs(i, j, v[cnt++]);
    
    for(PII a : v[0])
        for(PII b : v[1])
        {
            //printf("111");
            l = min(l, abs(a.s - b.s) + abs(a.f - b.f));
        }
    printf("%d", l - 1);
    return 0;
}

大臣的旅费


分析

从题目的描述中可以知道题目给的是一颗树。所以我们每次读取的边的数量就是n - 1(这个好像不用我说题目也说了)。而根据题目描述的所花路费只与总路程有关,路费是一个等差数列,所以对于这道题我们只需要求出路中的最长路径随后用n项和公式计算即可。

那么怎样求树中的最长路径呢?树的最长路径,其实就是树的直径

树的直径问题解法一边是使用两次dfs,一次找到离根节点(对于双向的树来说这个根可以是树中的任何点)最远的点,随后再用一次dfs遍历出的最长路径就是树的直径。

如何证明呢?

我们先来思考一下从一个点遍历一遍这棵树又回到起点的路径是多长,显而易见,这个路径长是边长和的二倍,在这条路中每条边是都被走了两遍的。

我们若想找到树中的直径显然是不能重复经过一条边的,所以直径要小于边长和的两倍

我们再思考,直径的理想情况是遍历树中的每一条边,这种情况是有可能的(一条直线),所以树的直径要小于等于树的边长和

铺垫完了这些我们就可以将求树的直径转化成树的边长和减去不经过的边长的长度,也就是:

d = l - x

d表示直径,l表示树边长之和,x表示未经过的边长,那么问题就转化成了求x的最小值

那么如何来求这个最小值呢?为方便理解,主包画了一个草图。

可以明显的看出直径就是最长的那一条(这是废话)。

我们按照我们的算法思路先以A为根节点找到D随后再一遍dfs找到C,那么从DC就是树的直径

为何这样是正确的呢?其实不难想,因为我们前面的分析,我们要求直径其实就是求避开的边长的最小值。

而我们在遍历树的过程中每次只记录距离最长的一条边(类似于dp),自然而然的就避开了所有短的边。

又根据树的性质两点之间的路径是唯一的,并且树中不存在环。所以就不存在图中那些弯弯绕,每次丢弃权重最小的一部分即可。

时间复杂度不必多说,两次都是O(n)


代码

/*
    树中的最长路,树的直径问题。
    先求长度,然后公式求解
*/
#include<iostream>
#include<vector>
#include<cstring>
#define s second
#define f first
using namespace std;
typedef pair<int, int> PII;
typedef long long LL;
const int N = 100010;
int n;
vector<int> tree[N];
vector<int> w[N];
bool read[N];
PII dfs(int v)
{
    PII l = {0, v};
    read[v] = true;
    for(int i = 0; i < tree[v].size(); i++)
    {
        int z = tree[v][i];
        if(!read[z])
        {
            PII m = dfs(z);
            if(m.f + w[v][i] > l.f)
                l = {m.f + w[v][i], m.s};
        }
    }
    return l; //最长距离
}

int main()
{
    scanf("%d", &n);
    for(int i = 1; i < n; i++)
    {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        tree[x].push_back(y);
        w[x].push_back(z);
        tree[y].push_back(x);
        w[y].push_back(z); 
    }
    int i = dfs(1).s;
    memset(read, false, sizeof read);
    i = dfs(i).f;
    //printf("%d", i);
    printf("%lld", ((LL)i * (11 + 10 + i))/2);
    return 0;
}

飞机降落


分析

第一眼感觉是贪心,但是看到数据量之后发现不是贪心,数据量很小所以我们考虑搜索状态压缩dp

初步分析,总共有n太飞机,每台飞机都要在固定的区间内降落。

发现n很小,我们可以考虑组合数,而组合数的时间复杂度是n * n!这道题就是36288000,三千六百万,再乘上t就是3.6亿,时间限制是两秒钟。

虽然组合数的常数很小可能通过但是也有TLE的风险,考虑优化。

优化不必多说,搜索的常见优化就是打表和剪枝,对于组合数问题打表显然不行,所以我们考虑剪枝

我们枚举组合数就是枚举每台飞机降落的顺序,显然后面的飞机不可能在前面的飞机降落前完成降落,所以剪枝的判断条件就是后面的飞机能否在前面的飞机完成降落后降落

还有一步贪心就是如果这台飞机可以降落,我们该选择哪个时间使其降落。很好想尽量让开始降落的时间靠前(和线性dp很像)因为降落时间早的状态是包含降落时间晚的所有状态的。

剪枝后运行时间是38ms,说明我们的代码效率还是很高的。(实际剪枝后的时间复杂度是接近O(n ^ 2)的)


代码

/*
    全排列降落顺序即可
*/
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N = 15;
int t, n;
int l[N], r[N], d[N];
vector<int> vtr;
bool read[N];

bool dfs(int t) 
{
    if(vtr.size() == n)
        return true;
    for(int i = 1; i <= n; i++)
    {
        if(!read[i])
        {
            if(l[i] + r[i] >= t) //可以放下
            {
                read[i] = true;
                vtr.push_back(i);
                if(dfs(max(t, l[i]) + d[i])) return true;
                read[i] = false;
                vtr.pop_back();
            }
        }
    }
    return false;
}
int main()
{
    scanf("%d", &t);
    while(t--)
    {
        memset(read, false, sizeof read);
        vtr.clear();
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
            scanf("%d%d%d", l + i, r + i, d + i);
        if(dfs(0))
            puts("YES");
        else
            puts("NO");
    }
    
}

母亲的牛奶


分析

不得不吐槽一下这个名字好怪……

看完题目后发现数据量很小,分析一下能不能直接用搜索

初步分析的话是可以的,但是我们发现一个问题,那就是不知道搜索什么时候结束。

这个简单,因为每次倒牛奶都不会有损失也不会有增加,所以根据某某定理(主包忘了),在之后某一个时间点的状态一定会和当前状态完全相同。

也就是一个循环,所以我们只需要存储一下每次遍历到的状态,当发现重复遍历时退出即可。

分析一下时间复杂度,我们分析极限情况

每个桶的大小是小于等于20的,也就是每个桶有21种状态。所以总的状态量就是21 * 21 * 21,大概是一万,通过本题绰绰有余。


代码

#include<iostream>
using namespace std;
const int N = 22;
bool read[N][N][N]; //dfs遇到重复状态时就退出
bool C[N]; 
int a, b, c;
void dfs(int x, int y, int z)
{
    if(read[x][y][z]) return;
    read[x][y][z] = true;
    if(!x)  C[z] = true;
   //printf("%d %d %d", x, y, z);
    if(x != 0)
    {
        dfs(max(0, x - (b - y)), min(b, y + x), z);
        dfs(max(0, x - (c - z)), y, min(c, z + x));
    }
    if(y != 0)
    {
        dfs(min(a, x + y), max(0, y - (a - x)), z);
        dfs(x, max(0, y - (c - z)), min(c, z + y));
    }
    if(z != 0)
    {
        dfs(min(a, x + z), y, max(0, z - (a - x)));
        dfs(x, min(b, y + z), max(0, z - (b - y)));
    }

}

int main()
{
    scanf("%d%d%d", &a, &b, &c);
    dfs(0, 0, c);
    for(int i = 0; i < 21; i++)
        if(C[i])
            printf("%d ", i);
    return 0;
}

扫雷


分析

今天的最后一道题(因为主包以前做过千奇百怪的扫雷游戏所以这种题感觉闭着眼睛都能写),不得不感慨,刷那么多题没记住几个,写着玩的东西倒是记忆犹新。

数据量是100 * 300 * 300,大概是1e7,所以我们需要将时间复杂度控制在线性

怎么写呢?我们先根据每个雷的位置处理出每个点应该有的数字,随后我们可以将0看为连通块,随后我们发现我们在点击非零块的时候一次只能解开一个,而在点击0时每次可以解开多个块(包括部分非0块)。

所以我们的思路是先解开0的连通块,随后再统计没有解开的非零块的数量即可。


代码

/*
    先找全0的连通块
*/
#include<iostream>
#include<cstring>
#define s second
#define f first
using namespace std;
typedef pair<int, int> PII;
const int N = 310;
int t, n;
char map[N][N];
bool read[N][N];
int st[N][N];
PII s[] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {-1, -1}, {1, 1}, {-1, 1}, {1, -1}};

void init()
{
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            if(map[i][j] == '*')
                for(int k = 0; k < 8; k++)
                    st[i + s[k].f][j + s[k].s]++; //统计数字
}

void dfs(int x, int y)
{
    read[x][y] = true;
    if(st[x][y] == 0 && map[x][y] == '.')
    {
        for(int i = 0; i < 8; i++)
            if(!read[x + s[i].f][y + s[i].s])
            {
                read[x + s[i].f][y + s[i].s] = true;
                dfs(x + s[i].f, y + s[i].s); //dfs求连通块
            }
    }
}

int main()
{
    scanf("%d", &t);
    for(int i = 1; i <= t; i++)
    {
        memset(read, 0, sizeof read);
        memset(st, 0, sizeof st);
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
            scanf("%s", &map[i][1]);
        init();
        /*for(int i = 1; i <= n;puts(""), i++)
            for(int j = 1; j <= n; j++)
                printf("%d ", st[i][j]);*/

        int l = 0;
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                if(map[i][j] == '.' && !read[i][j] && st[i][j] == 0)
                {
                    //printf("%d %d\n", i, j);
                    dfs(i, j);
                    l++;
                }
        //printf("%d\n", l); 
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                if(map[i][j] == '.' && !read[i][j])
                    l++;
        printf("Case #%d: %d\n", i, l);
    }
    
    return 0;
}
http://www.dtcms.com/a/98186.html

相关文章:

  • 【自用记录】本地关联GitHub以及遇到的问题
  • 从代码学习深度学习 - 使用块的网络(VGG)PyTorch版
  • 谈谈你对多态的理解
  • coding ability 展开第七幕(前缀和算法——进阶巩固)超详细!!!!
  • 算法基础——二叉树
  • Java 程序员面试题:从基础到高阶的深度解析
  • Elasticsearch 完全指南
  • 【HarmonyOS 5】初学者如何高效的学习鸿蒙?
  • Bitnode和Bitree有什么区别 为什么Bitree前多了*
  • 缴纳过路费--并查集+优先队列
  • Qt进阶开发:Graphics View图形视图框架
  • QT 跨平台发布指南
  • 枚举算法-day2
  • python 列表-元组-集合-字典
  • 软件工程之软件开发模型(瀑布、迭代、敏捷、DevOps)
  • 综述速读|086.04.24.Retrieval-Augmented Generation for AI-Generated Content A Survey
  • 深度学习处理时间序列(6)
  • 自学-python-基础-注释、数据类型、运算符、判断、循环
  • 树莓派超全系列文档--(13)如何使用raspi-config工具其二
  • 中断管理常用API详解(三)
  • flatMap 介绍及作用
  • C#连接sqlite数据库实现增删改查
  • 大模型最新面试题系列:微调篇之微调框架(二)
  • AI赋能python数据处理、分析与预测操作流程
  • Vue背景介绍+声明式渲染+数据响应式
  • 基于Baklib的云内容中台落地实践
  • JMeter运行日志详细分析
  • Kafka Stream从入门到精通:构建高吞吐、低延迟的实时流处理应用
  • 力扣.旋转矩阵Ⅱ
  • sqli-labs靶场 less6