
| 🔭 个人主页: 散峰而望 |
|---|
《C语言:从基础到进阶》《编程工具的下载和使用》《C语言刷题》
《C++》《算法竞赛从入门到获奖》《人工智能》《AI Agent》
愿为出海月,不做归山云
🎬博主简介



【算法练习】算法练习精选:从 Phone numbers 到 Decrease,覆盖字符串、模拟、图论思维题
- 前言
- [1. CF25B Phone numbers](#1. CF25B Phone numbers)
- [2. zzc 种田](#2. zzc 种田)
- [3. 信息传递](#3. 信息传递)
- [4. Decrease](#4. Decrease)
- 结语
前言
算法练习是编程学习路上不可或缺的一环。本次精选的四道题目,分别涉及字符串处理、数学思维、图论建模与差分数组技巧,难度由浅入深,覆盖了竞赛中常见的经典题型。每道题都配有详细的算法原理分析与完整可运行的参考代码,希望能帮助读者在实战中巩固基础、拓宽思路。话不多说,让我们开始今天的算法之旅吧!
1. CF25B Phone numbers

算法原理:
利用分类讨论。
判断一下 n 的奇偶:
- 如果是偶数,直接两个两个分开;
- 如果是奇数,两个两个分开,最后一个分三个。
参考代码:
cpp
#include <iostream>
using namespace std;
int main()
{
int n; string s;
cin >> n >> s;
if(n % 2) //奇数
{
for(int i = 0; i < n; i++)
{
cout << s[i];
if(i % 2 && i < n - 3) cout << '-';
}
}
else //偶数
{
for(int i = 0; i < n; i++)
{
cout << s[i];
if(i % 2 && i < n - 2) cout << '-';
}
}
return 0;
}
2. zzc 种田

算法原理:
数学。
每次都切出边长为 min(x, y) 的正方形,切完后,计算出新的 x 和 y ,然后继续切。
cpp
#include <iostream>
using namespace std;
typedef long long LL;
int main()
{
LL x, y; cin >> x >> y;
LL sum = 0;
while(x && y)
{
//假设 x > y,求出最小正方形有几个
LL cnt = x / y;
sum += cnt * y * 4;
//剩余的矩形的长
x %= y;
//确保矩形长大于宽
swap(x, y);
}
cout << sum << endl;
return 0;
}
3. 信息传递

算法原理:

拓扑排序 + DFS 计数。
- 拓扑排序的过程可以把除环的部分的点打上标记;
- 对没有打上标记的环进行一次 DFS,统计一下大小。
参考代码:
cpp
#include <iostream>
#include <queue>
using namespace std;
const int N = 2e5 + 10;
int n;
int ne[N]; //记录T
int in[N]; //记录入度信息
bool st[N]; //判断是否被标记
int cnt;
void dfs(int i)
{
cnt++;
st[i] = true;
int b = ne[i];
if(!st[b]) dfs(b);
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> ne[i];
in[ne[i]]++;
}
//1.利用拓扑排序把除环的部分的点打上标记
queue<int> q;
for(int i = 1; i <= n; i++)
{
if(in[i] == 0)
q.push(i);
}
while(q.size())
{
auto a = q.front(); q.pop();
st[a] = true;
int b = ne[a];
in[b]--;
if(in[b] == 0) q.push(b);
}
//2.利用dfs计算环的大小
int ret = n;
for(int i = 1; i <= n; i++)
{
if(!st[i])
{
//因为可能不止一个环,所以需要初始一下环的大小
cnt = 0;
dfs(i);
ret = min(cnt, ret);
}
}
cout << ret << endl;
return 0;
}
4. Decrease

算法原理:
根据连续子矩阵加 1 减 1 ,我们可以考虑二维差分。

为了方便进行「区间修改」操作,我们创建原矩阵的「差分矩阵」,记为 f f f。如果让原矩阵的「所有元素」都变成 0,相应的差分矩阵中的「所有元素」也都会变成 0。问题就转化成了把 f f f 矩阵变成 0 的最小操作次数。对原矩阵进行「区间」操作对应差分矩阵中「点」的操作,因此在原矩阵中把一个边长为 k k k 的子矩阵修改成 0,对应差分矩阵中「四个点」的修改。
那么我们就可以枚举「差分矩阵」中的左上角 ( i , j ) (i,j) (i,j),其中 1 ≤ i , j ≤ ( n − k + 1 ) 1 \le i,j \le (n - k + 1) 1≤i,j≤(n−k+1),然后进行「区间修改」操作:
- 如果 f i j fij fij 不等于 0,那我们就把这个区间变成 0,操作次数就是 a b s ( f i j ) abs(fij) abs(fij);
- 如果 f i j fij fij 等于 0,无需操作。
当把差分矩阵中 1 , n − k + 1 1,n - k + 1 1,n−k+1 区间内的所有元素「都变成 0」之后,扫描整个差分矩阵:
- 如果全变成 0,输出最小次数;
- 如果存在不等于 0 的位置,说明不能把整个矩阵变成 0,输出 − 1 -1 −1。
参考代码:
cpp
#include <iostream>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 5e3 + 10;
int n, m, k;
LL f[N][N]; // 差分
void insert(int x1, int y1, int x2, int y2, int k)
{
f[x1][y1] += k;
f[x1][y2 + 1] -= k;
f[x2 + 1][y1] -= k;
f[x2 + 1][y2 + 1] += k;
}
int main()
{
cin >> n >> m >> k;
for(int i = 1; i <= m; i++)
{
int x, y, z; cin >> x >> y >> z;
insert(x, y, x, y, z);
}
LL ret = 0;
for(int x1 = 1; x1 <= n - k + 1; x1++)
for(int y1 = 1; y1 <= n - k + 1; y1++)
{
ret += abs(f[x1][y1]);
int x2 = x1 + k - 1, y2 = y1 + k - 1;
insert(x1, y1, x2, y2, -f[x1][y1]);
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(f[i][j] != 0)
{
cout << -1 << endl;
return 0;
}
cout << ret << endl;
return 0;
}
结语
本次算法练习涵盖字符串、数学、图论与差分数组四类题型。分类讨论、贪心切割、拓扑找环、二维差分------每道题都锤炼了一种经典思维。算法无捷径,唯勤练与总结,方能游刃有余。
愿共渡重重浪,终见繁光远缀天。
