【笔试题汇总】字节跳动2024 春招第二场

这里是paoxiaomo,一个现役ACMer,之后将会持续更新算法笔记系列以及笔试题题解系列
本文章面向想打ICPC/蓝桥杯/天梯赛等程序设计竞赛,以及各个大厂笔试的选手
感谢大家的订阅➕ 和 喜欢💗
有什么想看的算法专题可以私信博主

(本文题面由清隆学长收集)

01.K小姐的基环树游戏

问题描述
K小姐最近迷上了一款基环树游戏。这个游戏的游戏地图是一个无向图,图中有 n n n 个节点和 m m m 条边,且不包含重边和自环。在这个游戏中,玩家需要在图上再选择连接一条边,使得整个无向图变成一棵基环树。
所谓基环树,是一种包含 n n n 个节点, n n n 条边,且不包含重边和自环的无向连通图。形象地说,基环树可以由一棵普通的树再添加一条之前不存在的边构成。
现在,K小姐想知道,对于当前的游戏地图,一共有多少种不同的连边方案可以使其变成一棵基环树呢?
输入格式
第一行输入两个正整数 n , m n,m n,m,代表无向图的节点数和边数。
接下来的 m m m 行,每行输入两个正整数 u , v u,v u,v,代表节点 u u u 和节点 v v v 之间有一条边相连。保证给定的无向图中不存在重边和自环。
输出格式
输出一个整数,代表添加一条新边使其变成基环树的方案数。
样例输入
4 4
1 2
1 3
2 3
2 4
样例输出
0
在这个样例中,原图已经是一棵基环树了,因此不需要再添加新边,方案数为0。
数据范围
1 ≤ n , m ≤ 1 0 5 1 \le n, m \le 10^5 1n,m105
1 ≤ u , v ≤ n 1 \le u, v \le n 1u,vn

【试题解析】

这个问题可以使用图论的知识来解决。基环树是一种特殊的图,它是一棵树加上一条额外的边构成的。要将一个无向图变成基环树,需要找到一种方法在当前的图上添加一条边,使得整个图成为基环树。
可以使用并查集来解决这个问题。首先,我们需要判断给定的无向图是否是一棵树,然后再找到可以添加的边数目。

#include <iostream>

using namespace std;

class UnionFind {//并查集板子
private:
    vector<int> parent, rank;
public:
    UnionFind(int n) {
        parent.resize(n);
        rank.resize(n, 0);
        for (int i = 0; i < n; ++i)
            parent[i] = i;
    }

    int find(int x) {
        return parent[x] == x ? x : (parent[x] = find(parent[x]));
    }

    void unite(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            if (rank[rootX] < rank[rootY])
                swap(rootX, rootY);
            parent[rootY] = rootX;
            if (rank[rootX] == rank[rootY])
                rank[rootX]++;
        }
    }
};

int main() {
    int n, m;
    cin >> n >> m;

    UnionFind uf(n);
    bool isTree = true;
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        if (uf.find(u - 1) == uf.find(v - 1)) {
            isTree = false;
        }
        uf.unite(u - 1, v - 1);
    }

    if (!isTree || m != n - 1) {
        cout << 0 << endl;
        return 0;
    }
    cout << 1 << endl;
    return 0;
}

02.魔法糖果屋

问题描述
小美是一个贪吃的小朋友,有一天她发现了一个装满魔法糖果的糖果屋。糖果屋里一共有 n n n 颗糖果,第 i i i 颗糖果上写着数字 a i a_i ai
小美每天都必须吃一颗糖果,而且第 x x x 天必须吃一颗写着数字 x x x 的糖果。小美有一个魔法棒,可以用来改变一颗糖果上的数字,将其变成原来的两倍。但魔法棒只能使用一次。
小美想知道使用魔法棒一次之后,自己最多能连续吃到糖果的天数是多少呢?你能帮帮她吗?
输入格式
第一行包含一个正整数 n n n,表示糖果的数量。
第二行包含 n n n 个正整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,,an,表示每颗糖果上写的数字。
输出格式
输出一个整数,表示小美最多能连续吃到糖果的天数。
样例输入
4
1 1 3 5
样例输出
3
数据范围
1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1n105
1 ≤ a i ≤ 1 0 9 1 \le a_i \le 10^9 1ai109

【试题解析】

这个问题可以通过贪心算法来解决。我们可以先对糖果按照数字进行排序,然后从最小的数字开始吃,尽可能地连续吃到与糖果数字对应的天数。在遍历糖果时,如果当前糖果的数字小于等于连续吃到的天数,那么可以尝试使用魔法棒将其变成原来的两倍,这样可以延长连续吃到的天数。

#include <bits/stdc++.h>
using namespace std;

int maxDays(vector<int>& candies) {
    sort(candies.begin(), candies.end());
    int n = candies.size();
    unordered_map<int, int> cnt;
    for (int candy : candies) {
        cnt[candy]++;
    }

    int last = candies[0];
    unordered_map<int, int> leftRange, rightRange;
    leftRange[candies[0]] = candies[0];
    int res = 0;

    for (int i = 0; i < n - 1; i++) {
        if (candies[i + 1] != candies[i] + 1) {
            leftRange[last] = candies[i];
            rightRange[candies[i]] = last;
            res = max(res, candies[i] - last + 1);
            last = candies[i + 1];
            leftRange[last] = candies[i + 1];
        }
    }

    if (last != candies.back()) {
        leftRange[last] = candies.back();
        rightRange[candies.back()] = last;
        res = max(res, candies.back() - last + 1);
    }

    for (int candy : candies) {
        int doubleCandy = candy * 2;
        int r = -1;
        if (leftRange.count(candy) && cnt[candy] < 2) {
            r = leftRange[candy];
            if (r == candy) {
                rightRange.erase(r);
            } else {
                rightRange[r] = candy + 1;
            }
        }

        int ans = 1;
        if (leftRange.count(doubleCandy + 1)) {
            ans += leftRange[doubleCandy + 1] - doubleCandy;
        }
        if (rightRange.count(doubleCandy - 1)) {
            if (cnt[candy] >= 2) {
                ans += doubleCandy - rightRange[doubleCandy - 1];
            } else {
                ans += doubleCandy - max(rightRange[doubleCandy - 1], candy + 1);
            }
        }
        res = max(res, ans);

        if (r != -1) {
            rightRange[r] = candy;
        }
    }

    return res;
}

03.K小姐的树形公园设计

题目描述
K小姐是一名园林设计师,最近她接到了一个新的任务。任务是设计一个树形公园,公园由 n n n 个节点组成,节点之间由 n − 1 n-1 n1 条无向边连接。公园建成后,为了方便游客游览,K小姐需要在公园中选择一个节点建立一个游客中心。
建立游客中心后,公园会被分割成若干个连通块。为了避免某些连通块游客过于拥挤,K小姐希望最大的连通块的节点数量尽可能小。但是她发现要找到最优的游客中心位置并不容易。因此,她决定随机选择一个节点作为游客中心的位置。
现在,K小姐想知道,如果随机选择一个节点作为游客中心,最大连通块节点数量的期望值是多少呢?
输入格式
第一行包含一个正整数 n n n,表示树形公园的节点数量。
接下来 n − 1 n-1 n1 行,每行包含两个正整数 u u u v v v,表示节点 u u u 和节点 v v v 之间有一条无向边相连。
输出格式
输出一个实数,表示最大连通块节点数量的期望值,与标准答案误差不超过 1 0 − 6 10^{-6} 106 即视为正确。
样例输入
2
1 2
样例输出
1.000000000
样例说明
无论选择节点 1 还是节点 2 作为游客中心,最大连通块的节点数量均为 1。
数据范围
1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1n105
1 ≤ u , v ≤ n 1 \le u, v \le n 1u,vn

【试题解析】

这个问题可以通过动态规划和树的性质来解决。我们可以使用深度优先搜索来遍历树,对于每个节点,计算以该节点为根的子树中最大连通块的节点数量。然后,对于每个节点,将其子树中最大连通块的节点数量加上该节点本身,得到以该节点为游客中心时最大连通块的节点数量。最后,将所有节点作为游客中心时的最大连通块的节点数量取平均,即为所求的期望值。

#include <iostream>
#include <vector>

using namespace std;

vector<vector<int>> adj;
vector<bool> visited;
vector<int> subtree_size;

void dfs(int node, int parent) {
    subtree_size[node] = 1;
    for (int neighbor : adj[node]) {
        if (neighbor != parent) {
            dfs(neighbor, node);
            subtree_size[node] += subtree_size[neighbor];
        }
    }
}

int main() {
    int n;
    cin >> n;

    adj.resize(n);
    visited.assign(n, false);
    subtree_size.assign(n, 0);

    for (int i = 0; i < n - 1; ++i) {
        int u, v;
        cin >> u >> v;
        adj[u - 1].push_back(v - 1);
        adj[v - 1].push_back(u - 1);
    }

    dfs(0, -1); // Start DFS from node 0

    double expected_max_size = 0.0;
    for (int i = 0; i < n; ++i) {
        expected_max_size += subtree_size[i];
    }
    expected_max_size /= n;

    cout << fixed;
    cout.precision(6);
    cout << expected_max_size << endl;

    return 0;
}

04.LYA的光纤网络设计

问题描述
LYA是一家通信公司的工程师,她正在设计一个新的光纤网络。这个网络需要在一条直线上建设 5 5 5 个信号塔,用于传输信号。
直线上有 n n n 个可以建设信号塔的位置,其中第 1 1 1 个位置和第 n n n 个位置必须建设信号塔。每个位置都有一个建设强度 a i a_i ai,代表在该位置建设信号塔的设备质量。
信号在两个信号塔之间传输时,信号强度会发生变化。信号强度的变化值与两个信号塔的建设强度之和成正比,与两个信号塔之间的距离成反比。具体来说,如果两个信号塔的建设强度分别为 a a a b b b,它们之间的距离为 d d d,则一个强度为 x x x 的信号经过这两个信号塔传输后,强度将变为 x × a + b d x \times \frac{a+b}{d} x×da+b
LYA需要在剩余的 n − 2 n-2 n2 个位置中选择 3 3 3 个建设信号塔,使得从最左侧的信号塔传输到最右侧信号塔时,信号的强度尽可能大。假设最左侧信号塔发出的初始信号强度为 1 1 1。请你帮助LYA设计出这个光纤网络,计算出信号强度的最大值。
输入格式
第一行包含一个正整数 n n n,表示可以建设信号塔的位置数量。
第二行包含 n n n 个正整数 x 1 , x 2 , … , x n x_1, x_2, \ldots, x_n x1,x2,,xn,其中 x i x_i xi 表示第 i i i 个位置的坐标。
第三行包含 n n n 个正整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,,an,其中 a i a_i ai 表示在第 i i i 个位置建设信号塔的建设强度。
输出格式
输出一个实数,表示信号强度的最大值。与标准答案误差不超过 1 0 − 6 10^{-6} 106 即视为正确。
样例输入
6
1 3 5 7 8 10
7 6 9 5 1 3
样例输出
910.0000000000
样例说明
在第 1 1 1 2 2 2 3 3 3 4 4 4 6 6 6 这五个位置建设信号塔是最优方案。第 1 1 1 个信号塔发出强度为 1 1 1 的信号:

  • 传输到第 2 2 2 个信号塔后,信号强度变为 6.5 6.5 6.5
  • 传输到第 3 3 3 个信号塔后,信号强度变为 48.75 48.75 48.75
  • 传输到第 4 4 4 个信号塔后,信号强度变为 341.25 341.25 341.25
  • 传输到第 6 6 6 个信号塔后,信号强度变为 910 910 910
    可以证明这是最优的建设方案。
    数据范围
    5 ≤ n ≤ 2000 5 \le n \le 2000 5n2000
    1 ≤ x i , a i ≤ 1 0 4 1 \le x_i, a_i \le 10^4 1xi,ai104
    x 1 < x 2 < … < x n x_1 < x_2 < \ldots < x_n x1<x2<<xn

【试题解析】

这个问题可以通过动态规划来解决。我们可以将问题转化为从左到右选择 3 3 3 个位置建设信号塔,使得从最左侧的信号塔传输到最右侧信号塔时,信号的强度尽可能大。我们可以使用动态规划来求解这个问题。

定义状态 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示在前 i i i 个位置中选择 j j j 个位置建设信号塔,并且第 j j j 个位置为第 k k k 个位置时,信号强度的最大值。则状态转移方程为:

d p [ i ] [ j ] [ k ] = max ⁡ 0 ≤ l < k { d p [ i − 1 ] [ j − 1 ] [ l ] × a k + a l ∣ x k − x l ∣ } dp[i][j][k] = \max_{0 \leq l < k}\{dp[i-1][j-1][l] \times \frac{a_k + a_{l}}{|x_k - x_l|}\} dp[i][j][k]=0l<kmax{dp[i1][j1][l]×xkxlak+al}

最终答案为 max ⁡ 0 ≤ l < n { d p [ n − 1 ] [ 3 ] [ l ] } \max_{0 \leq l < n}\{dp[n-1][3][l]\} max0l<n{dp[n1][3][l]}

#include <bits/stdc++.h>


using namespace std;

const double INF = 1e18;

int main() {
    int n;
    cin >> n;

    vector<int> x(n), a(n);
    for (int i = 0; i < n; ++i) {
        cin >> x[i];
    }
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }

    vector<vector<vector<double>>> dp(n, vector<vector<double>>(4, vector<double>(n, -INF)));

    for (int i = 0; i < n; ++i) {
        dp[i][1][i] = 1.0;
    }

    for (int i = 1; i < n; ++i) {
        for (int j = 2; j <= min(3, i + 1); ++j) {
            for (int k = 0; k < i; ++k) {
                for (int l = 0; l < k; ++l) {
                    dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - 1][l] * a[k] / abs(x[k] - x[l]));
                }
            }
        }
    }

    double ans = 0.0;
    for (int i = 0; i < n; ++i) {
        ans = max(ans, dp[n - 1][3][i]);
    }

    cout << ans << endl;

    return 0;
}
整理试题不易,你的关注是我更新的最大动力!关注博主 带你看更多面试及竞赛试题和实用算法。
  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值