这里是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
1≤n,m≤105
1
≤
u
,
v
≤
n
1 \le u, v \le n
1≤u,v≤n
【试题解析】
这个问题可以使用图论的知识来解决。基环树是一种特殊的图,它是一棵树加上一条额外的边构成的。要将一个无向图变成基环树,需要找到一种方法在当前的图上添加一条边,使得整个图成为基环树。
可以使用并查集来解决这个问题。首先,我们需要判断给定的无向图是否是一棵树,然后再找到可以添加的边数目。
#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
1≤n≤105
1
≤
a
i
≤
1
0
9
1 \le a_i \le 10^9
1≤ai≤109
【试题解析】
这个问题可以通过贪心算法来解决。我们可以先对糖果按照数字进行排序,然后从最小的数字开始吃,尽可能地连续吃到与糖果数字对应的天数。在遍历糖果时,如果当前糖果的数字小于等于连续吃到的天数,那么可以尝试使用魔法棒将其变成原来的两倍,这样可以延长连续吃到的天数。
#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
n−1 条无向边连接。公园建成后,为了方便游客游览,K小姐需要在公园中选择一个节点建立一个游客中心。
建立游客中心后,公园会被分割成若干个连通块。为了避免某些连通块游客过于拥挤,K小姐希望最大的连通块的节点数量尽可能小。但是她发现要找到最优的游客中心位置并不容易。因此,她决定随机选择一个节点作为游客中心的位置。
现在,K小姐想知道,如果随机选择一个节点作为游客中心,最大连通块节点数量的期望值是多少呢?
输入格式
第一行包含一个正整数
n
n
n,表示树形公园的节点数量。
接下来
n
−
1
n-1
n−1 行,每行包含两个正整数
u
u
u 和
v
v
v,表示节点
u
u
u 和节点
v
v
v 之间有一条无向边相连。
输出格式
输出一个实数,表示最大连通块节点数量的期望值,与标准答案误差不超过
1
0
−
6
10^{-6}
10−6 即视为正确。
样例输入
2
1 2
样例输出
1.000000000
样例说明
无论选择节点 1 还是节点 2 作为游客中心,最大连通块的节点数量均为 1。
数据范围
1
≤
n
≤
1
0
5
1 \le n \le 10^5
1≤n≤105
1
≤
u
,
v
≤
n
1 \le u, v \le n
1≤u,v≤n
【试题解析】
这个问题可以通过动态规划和树的性质来解决。我们可以使用深度优先搜索来遍历树,对于每个节点,计算以该节点为根的子树中最大连通块的节点数量。然后,对于每个节点,将其子树中最大连通块的节点数量加上该节点本身,得到以该节点为游客中心时最大连通块的节点数量。最后,将所有节点作为游客中心时的最大连通块的节点数量取平均,即为所求的期望值。
#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
n−2 个位置中选择
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}
10−6 即视为正确。
样例输入
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 5≤n≤2000
1 ≤ x i , a i ≤ 1 0 4 1 \le x_i, a_i \le 10^4 1≤xi,ai≤104
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]=0≤l<kmax{dp[i−1][j−1][l]×∣xk−xl∣ak+al}
最终答案为 max 0 ≤ l < n { d p [ n − 1 ] [ 3 ] [ l ] } \max_{0 \leq l < n}\{dp[n-1][3][l]\} max0≤l<n{dp[n−1][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;
}