算法题练习(第三周)
Week 3
P1396 营救
标签
图论 二分 并查集 最短路 生成树
分析
-
题目 原题大意就是需要我们找到一条路径,使得路径中的最大拥挤度最小。这类似于在图中寻找最小生成树中的最大边,因为最小生成树保证了所有边的权值之和最小,但我们需要的是路径中的最大边权值最小。
-
做法 使用 \(Kruskal\) 算法,按拥挤度从小到大排序所有边,逐步将边加入并查集,直到起点
s和终点t连通。此时最后加入的边的拥挤度即为路径中的最大拥挤度。 -
优化 使用路径压缩和按秩合并来优化并查集操作,以提高效率。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e6 + 10;
struct Edge {
int x, y, t;
} a[MAXN];
int father[MAXN], n, m, s, t;
int find(int k) {
if (father[k] == k)
return k;
return father[k] = find(father[k]);
}
bool cmp(Edge p, Edge q) {
return p.t < q.t;
}
int main() {
scanf("%d%d%d%d", &n, &m, &s, &t);
for (int i = 0; i <= MAXN; i++)
father[i] = i;
for (int i = 1; i <= m; i++)
cin >> a[i].x >> a[i].y >> a[i].t;
sort(a + 1, a + m + 1, cmp);
for (int i = 1; i <= m; i++) {
father[find(a[i].x)] = find(a[i].y);
if (find(s) == find(t)) {
printf("%d", a[i].t);
return 0;
}
}
return 0;
}
解释
- 数据结构定义:
-
Edge结构体存储每条边的起点、终点和拥挤度。 -
father数组用于并查集操作,记录每个节点的父节点。
- 并查集操作:
-
find函数使用路径压缩优化,确保树的高度最小。 -
28 行 函数合并两个集合,这里使用简单的合并策略,未按秩合并,但足够高效。
-
主函数流程:
-
初始化并查集,每个节点的父节点初始化为自身。
-
读取所有边并按拥挤度排序。
-
逐个合并边,直到起点和终点连通,输出当前边的拥挤度作为结果。
总结
这是一道比较有意思的题目,既检查了之前联系过的并查集模板题目,又考察新的 \(Kruskal\) 算法。难度大概是中位黄。
P4391 [BalticOI 2009] Radio Transmission 无线传输
标签
字符串 前缀和 KMP算法
分析
这一题的关键思路在于如何把 KMP 算法的 next 数组转换成题目中的需要求的东西。
我们知道了原字符串ss除去公共前后缀中的一个剩下的就是循环子串。同样,易知原串ss除去开头(或结尾)的循环子串剩下的部分就是公共前后缀。
至此,问题中要求的原字符串ss的最小循环子串,就转化成了求原字符串ss的最大公共前后缀。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1111111;
int n, kmp[MAXN];
string s;
int main() {
scanf("%d", &n);
cin >> s;
s = " " + s;
int j = 0;
for (int i = 2; i <= n; ++i) {
while (j && s[i] != s[j + 1])
j = kmp[j];
if (s[i] == s[j + 1])
++j;
kmp[i] = j;
}
printf("%d", n - kmp[n]);
return 0;
}
解析
- 输入处理
读取字符串长度 n 和字符串 s。
在字符串前添加空格使其变为 1-based 索引,方便后续处理。
- KMP部分匹配表构建
kmp[i] 表示前 i 个字符的最长公共前后缀长度。
初始化 j=0 表示当前匹配的前缀长度。
遍历字符串(从第2个字符开始),通过回溯机制更新 j:
失配时回溯:若当前字符不匹配,j 回退到 kmp[j] 的位置。
匹配时扩展:若字符匹配,则扩展前缀长度 j。
最终 kmp[i] 记录前 i 个字符的最长公共前后缀。
- 最短循环节计算
根据 KMP 特性,整个字符串的最短循环节长度为 n - kmp[n]。
例如,若字符串由某个子串重复多次构成,则 kmp[n] 表示总长度减去一个完整循环节的长度。
总结
这是一道需要经过辨析才能做对的题目,题目将KMP的概念隐藏在一些细节之中,是一道合适的 中上位绿。
P6051 [RC-02] 求和
标签
字符串
分析
分析题目,我们只需要找到数字出现的地方然后通过十进制算术法算出数值进行累加,主要问题是正负号的处理,所以我们需要找到符号出现的前后是否会有数字,前面有数字说明是一个普通符号,前面没有后面有说明是一个负号。
代码
#include<bits/stdc++.h>
using namespace std;
string s;
int main(){
while(getline(cin, s)){
int ans = 0;
bool m = false;
for(int i = 0; i < s.size(); i++) {
if(s[i] >= '0' && s[i] <= '9'){
int j = i, sum = 0;
while(s[j] >= '0' && s[j] <= '9')
sum = sum * 10 + (s[j] - '0'), j++;
if(s[i - 1] == '-' && (s[i - 2] < '0' || s[i - 2] > '9'))
ans -= sum;
else
ans += sum;
i = j, m = true;
}
}
if(m == true)
cout << ans << endl;
}
return 0;
}
解释
-
读入 : 需要使用
getline,不然无法读入空格。 -
遍历 : 整个字符串,按照刚才分析的进行操作。
-
输出 : 需要进行判断是否存在数字。
总结
这是一道比较简单的下位黄题,比较需要考虑的是正负数的处理。
P5635 【CSGRound1】天下第一
标签
模拟 搜索 记忆化搜索
分析
为了解决这个问题,我们需要判断两个玩家在交替更新数值时的胜负情况,或者是否会出现平局。为了避免递归导致的内存溢出问题,我们可以使用快慢指针法(Floyd判圈算法)来检测循环。
代码
#include <bits/stdc++.h>
using namespace std;
pair<int, int> ns(int x, int y, int mod) {
int new_x = (x + y) % mod;
int new_y = (new_x + y) % mod;
return {new_x, new_y};
}
int fun(int x, int y, int mod) {
if (x == 0)
return 1;
if (y == 0)
return 2;
int sx = x, sy = y;
int fx = x, fy = y;
while (true) {
auto [sx1, sy1] = ns(sx, sy, mod);
sx = sx1;
sy = sy1;
if (sx == 0)
return 1;
if (sy == 0)
return 2;
auto [fx1, fy1] = ns(fx, fy, mod);
auto [fx2, fy2] = ns(fx1, fy1, mod);
fx = fx2;
fy = fy2;
if (fx == 0)
return 1;
if (fy == 0)
return 2;
if (sx == fx && sy == fy) {
return -1;
}
}
}
int main() {
int T, mod;
scanf("%d %d", &T, &mod);
while (T--) {
int x, y;
scanf("%d %d", &x, &y);
x %= mod;
y %= mod;
if (x == 0) {
printf("1\n");
continue;
}
if (y == 0) {
printf("2\n");
continue;
}
int result = fun(x, y, mod);
if (result == -1) {
printf("error\n");
} else {
printf("%d\n", result);
}
}
return 0;
}
分析
-
问题分析:两个玩家轮流更新数值,每次更新后的数值取模。我们需要判断哪一方先使自己的数值变为0,或者是否进入循环导致平局。
-
算法选择:使用快慢指针法检测循环。快指针每次移动两步,慢指针每次移动一步。如果两者相遇,说明进入循环;如果任一数值变为0,则判断胜负。
-
复杂度分析:时间复杂度取决于循环的长度,空间复杂度为O(1),因为我们只使用了常数空间。
总结
这道题非常好的解释了Floyd算法在某些区域的特用,是一个有趣的中位黄。
P5535 【XR-3】小道消息
标签
数学 素数判断,质数,筛法 最大公约数 gcd
分析
-
质数判断:首先判断初始消息接收者的数是否为质数。如果是质数,则进一步判断该质数的两倍是否大于所有人中最大的数(即n+1)。如果成立,消息将在1天内传播完毕,否则需要2天。
-
合数处理:如果初始数不是质数,根据伯特兰-切比雪夫定理,存在一个质数在初始数和其两倍之间,消息可以通过该质数在2天内传播到所有人。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n, k;
bool is_prime(ll s) {
if (s <= 1) return false;
for (ll i = 2; i * i <= s; ++i) {
if (s % i == 0) return false;
}
return true;
}
int main() {
cin >> n >> k;
ll s = k + 1;
if (is_prime(s)) {
if (2 * s > n + 1) {
cout << 1;
} else {
cout << 2;
}
} else {
cout << 2;
}
return 0;
}
解释
-
质数判断函数:is_prime函数通过试除法判断一个数是否为质数,遍历2到该数的平方根。
-
主函数:读取输入的n和k,计算初始消息接收者的数s。根据s是否为质数及其倍数关系确定传播所需天数:
-
如果s是质数且其两倍大于n+1,输出1天。
-
其他情况(包括s是质数但两倍不大于n+1,或s是合数),输出2天。
总结
这是一道充分使用数论知识 伯特兰-切比雪夫定理 的题目,调用了学者的思维与动脑能力。/bushi
P5823 【L&K R-03】课表的排列
标签
数学
分析
这道题只需要我们输出一个正确样例就够了,那么我们就尽量构建简单的数据。
多造几组数据发现:
$ 1 \sim n $ $ 1 \sim n 的奇数 $ $ 1 \sim n 的偶数 $
这样的数据完全满足题目条件。
但是考试时不能冒险用这种离谱推出来的结论,所以我们要给出证明。
证:
分类讨论:
如果 $ i = 2k + 1 $
作图
.bmp?t=1742386644&download=true)
我们推导一下,得到两个 \(i\) 的距离为:
$ \displaystyle\frac{i+1}{2} + (n - i) - 1 $
化简:
$ n - \displaystyle\frac{i + 1}{2} $
如果 $ i = 2k $
作图
.bmp?t=1742386853&download=true)
我们推导一下,得到两个 \(i\) 的距离为:
$ \displaystyle\frac{i}{2} + \frac{n+1}{2} + (n+i) - 1 $
化简:
$ \displaystyle\frac{3n - i - 1}{2} $
对于上面两个式子,我们构造连续的数字(分为奇偶数进行带入),可以得出公差为 \(1\) 满足题意。
证毕。
代码
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++) cout<<i<<" ";
for(int i=1;i<=n;i+=2) cout<<i<<" ";
for(int i=2;i<=n;i+=2) cout<<i<<" ";
return 0;
}
解析
应该不必多说了吧。
先输出不大于 $ n $ 的正整数。
再输出不大于 $ n $ 的正奇数。
再输出不大于 $ n $ 的正偶数。
总结
这道题就是我们非常喜欢的找规律题,独特在如果不使用题目想让我们使用的方法是不大容易做出来的,总之这是一道很有趣的下位黄。、
P6160 [Cnoi2020] 向量
标签
向量
分析
问题分析:
向量内积的和最小化取决于三个向量的方向安排。
当三个向量的模长满足三角不等式时(即最大模长不超过另外两个模长之和),它们可以形成一个闭合三角形,此时内积和的最小值为负的平方和的一半。
当不满足三角不等式时,最大的向量将主导方向,此时内积和的最小值由线性组合决定。
关键观察:
闭合三角形情况: 当三个向量可以形成闭合三角形时,内积和为负的平方和的一半。
非闭合情况: 当最大的向量无法被另外两个向量平衡时,内积和由线性组合决定。
算法选择:
直接根据输入参数判断是否满足三角不等式,选择对应的公式计算结果。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
long double r1, r2, r3;
cin>>r1>>r2>>r3;
long double result=(r3 <= r1 + r2) ? -(r1*r1 + r2*r2 + r3*r3) / 2.0 : r1 * r2 - r3 * (r1 + r2);
cout<<fixed<<setprecision(1)<<result;
return 0;
}
解释
输入处理:读取三个整数r1, r2, r3。
条件判断:
当 \(r3 \le r1 + r2\) 时,三个向量可以形成闭合三角形,此时最小值由公式 \(-\frac{r1^2 + r2^2 + r3^2}{2}\) 计算。
总结
这是一道非常经典的高中数学题目,运用了经典的向量,帮助我们熟悉三角函数与向量的关系,难度在中位黄。
否则,最小值由公式 $ r1 \times r2 - r3 \times (r1 + r2) $ 计算。
P6599 「EZEC-2」异或
标签
数学 贪心 进制 位运算 构造
分析
为了解决这个问题,我们需要构造一个长度为 l 的正整数序列,使得所有元素对的异或和最大化。关键在于如何高效地计算每个二进制位对最终结果的贡献。
-
按位处理:对于每个二进制位 k,我们分别考虑其贡献。异或运算的性质是,只有当两个数的某一位不同时,该位才会对结果产生贡献。
-
计算可能的值:对于每个二进制位 k,计算在 [1, n] 范围内该位为 1 的数的数量(记为 cnt)和该位为 0 的数的数量(记为 tot)。
-
判断条件:如果 cnt 和 tot 都不为零,则该位的贡献为所有可能对数的最大值乘以该位的权值。否则,该位的贡献为零。
-
累加结果:将所有位的贡献累加,并对结果取模。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
int main() {
int T;
scanf("%d", &T);
while (T--) {
ll n, l;
scanf("%lld%lld", &n, &l);
ll ans = 0;
for (int k = 0; k <= 40; k++) {
ll m = 1LL << k;
if (m > n)
continue;
ll h = (m << 1) - 1;
if (h > n)
h = n;
ll cnt = h - m + 1;
ll tot = n - cnt;
if (cnt >= 1 && tot >= 1) {
ll ct = (l / 2) * ((l + 1) / 2) % MOD;
ct = ct * (m % MOD) % MOD;
ans = (ans + ct) % MOD;
}
}
printf("%lld\n", ans % MOD);
}
return 0;
}
分析
-
输入处理:使用
scanf和printf优化,处理多组测试数据。 -
遍历每个二进制位:从最低位到最高位(最多到 40 位,因为 2^40 超过 1e12)。
-
计算每个位的贡献:
-
确定当前位 k 的权值 m。
-
计算在 [1, n] 范围内该位为 1 的数的数量 cnt 和该位为 0 的数的数量 total_zero。
-
如果两者都不为零,则计算该位的最大贡献,即可能的对数乘以权值。
- 累加结果并取模:将所有位的贡献累加,并处理大数取模。
总结
该方法高效地利用了二进制位的特性,确保了在 O(1) 时间内处理每个二进制位,从而在整体复杂度上能够处理大规模输入。同时,这道题也是一道很有思考意义的上位黄。
周总结
这一周主要是完成了关于算法模板的类型题目。
例如:生成树 KMP 字符串 记忆化搜索 最大公约数 数学 向量 位运算 等
总共完成 14 道题,写了 8 道题的题解。
总体评价是中等,主要希望能掌握知识点。

浙公网安备 33010602011771号