1. 为什么选择洛谷作为算法刷题平台
作为一名从大学ACM竞赛一路走来的算法爱好者,我深知持续刷题对保持编程敏感度的重要性。在尝试过LeetCode、Codeforces等多个平台后,我最终将洛谷(luogu.com.cn)作为主力刷题平台,原因有三:
首先是题目质量。洛谷的题目库覆盖了NOIP/CSP等国内主流算法竞赛的真题和模拟题,相比国际平台的题目更贴近国内选手的训练需求。平台按照难度分级明确,从入门到省选/NOI级别都有对应题库,特别适合想要系统性提升的选手。
其次是社区生态。每道题目下方的题解区堪称宝藏,经常能看到金牌选手分享的多种解法。比如P1001 A+B Problem这种基础题,居然有人用FFT(快速傅里叶变换)来解,这种"过度工程化"的解法虽然实战无用,但对开拓思维极有帮助。
最后是评测体验。洛谷的在线评测系统支持C++11/14/17、Python3、Java8等主流语言,执行效率高且反馈详细。特别是对C++选手,相比某些平台默认开启-O2优化的"玄学"环境,洛谷允许自定义编译选项,更接近线下竞赛的真实环境。
提示:新人建议从「官方题单」入手,比如「洛谷官方精选-入门篇」包含100道经典题目,按知识点分类,比盲目刷题效率高3倍以上。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 我的第六天刷题实战记录
2.1 晨间热身:线性表基础
今天以P3156 【深基15.例1】询问学号开场。这道题考察基础的数组索引操作,题目要求根据输入的学号列表,快速回答第i个到校学生的学号。看似简单,但有两个关键点需要注意:
- 学号范围是1e7量级,但人数只有2e5,直接用数组存储会MLE(内存超限)
- 输入规模达到2e5,需要使用快速的IO方法
我的AC代码选择了vector存储+scanf/printf的IO方案:
cpp复制#include <cstdio>
#include <vector>
using namespace std;
int main() {
vector<int> students;
int n, m, tmp;
scanf("%d%d", &n, &m);
while(n--) {
scanf("%d", &tmp);
students.push_back(tmp);
}
while(m--) {
scanf("%d", &tmp);
printf("%d\n", students[tmp-1]);
}
return 0;
}
2.2 午后攻坚:贪心算法进阶
P1090 [NOIP2004 提高组] 合并果子是经典的贪心算法题。最初我用优先队列(小根堆)实现,时间复杂度O(nlogn),轻松AC:
cpp复制priority_queue<int, vector<int>, greater<int>> q;
int main() {
int n, x, ans = 0;
cin >> n;
while(n--) {
cin >> x;
q.push(x);
}
while(q.size() > 1) {
int a = q.top(); q.pop();
int b = q.top(); q.pop();
ans += a + b;
q.push(a + b);
}
cout << ans;
return 0;
}
但随后我尝试用更高效的O(n)解法——双队列法。将原始果子排序后存入队列1,合并后的果子存入队列2,每次取两个队列前端的最小值:
cpp复制deque<int> q1, q2;
int main() {
int n, x, ans = 0;
cin >> n;
vector<int> v(n);
for(int i=0; i<n; ++i) cin >> v[i];
sort(v.begin(), v.end());
for(auto x : v) q1.push_back(x);
while(q1.size() + q2.size() > 1) {
int a, b;
// 取最小值逻辑
if(q2.empty() || (!q1.empty() && q1.front() < q2.front())) {
a = q1.front(); q1.pop_front();
} else {
a = q2.front(); q2.pop_front();
}
// 同上取次小值
if(q2.empty() || (!q1.empty() && q1.front() < q2.front())) {
b = q1.front(); q1.pop_front();
} else {
b = q2.front(); q2.pop_front();
}
ans += a + b;
q2.push_back(a + b);
}
cout << ans;
return 0;
}
实测当n=1e5时,优先队列版本耗时约200ms,而双队列版本仅需80ms,优化效果显著。
2.3 晚间挑战:动态规划专项
P1048 [NOIP2005 普及组] 采药是经典的01背包问题。我首先用二维DP实现:
cpp复制int dp[105][1005]; // dp[i][j]表示前i个物品j时间内的最大价值
int main() {
int T, M;
cin >> T >> M;
for(int i=1; i<=M; ++i) {
int t, v;
cin >> t >> v;
for(int j=0; j<=T; ++j) {
if(j < t) dp[i][j] = dp[i-1][j];
else dp[i][j] = max(dp[i-1][j], dp[i-1][j-t] + v);
}
}
cout << dp[M][T];
return 0;
}
然后优化为一维滚动数组,注意内层需要逆序遍历:
cpp复制int dp[1005]; // dp[j]表示j时间内的最大价值
int main() {
int T, M;
cin >> T >> M;
for(int i=1; i<=M; ++i) {
int t, v;
cin >> t >> v;
for(int j=T; j>=t; --j) { // 必须逆序!
dp[j] = max(dp[j], dp[j-t] + v);
}
}
cout << dp[T];
return 0;
}
3. 刷题过程中的经验沉淀
3.1 输入输出优化技巧
在处理大规模数据时(如n≥1e5),C++的cin/cout会明显拖慢速度。有两种解决方案:
- 在main函数开头添加:
cpp复制ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
这可以关闭C++流与C流的同步,提升约3倍速度。
- 直接使用C风格的scanf/printf,这在极端情况下(如n=1e6)仍有优势。
注意:混用cin和scanf会导致输入错乱,务必统一风格
3.2 调试技巧:对拍验证
当遇到WA(Wrong Answer)时,可以编写暴力程序与优化程序进行对拍:
- 编写暴力解法(保证正确性但效率低)
- 生成随机测试数据
- 比较两个程序的输出差异
例如测试合并果子问题时,可以这样生成数据:
cpp复制srand(time(0));
int n = rand() % 100 + 1;
cout << n << endl;
for(int i=0; i<n; ++i)
cout << rand() % 10000 + 1 << " ";
3.3 时间复杂度估算方法
通过以下公式预判算法是否可能TLE(Time Limit Exceeded):
code复制最大数据规模n | 可接受的时间复杂度
1e6 | O(n)或O(nlogn)
1e5 | O(nlogn)
1e4 | O(n^2)
1e3 | O(n^2logn)
例如当n=1e5时,O(n^2)的算法必然超时(1e10次操作),需要优化为O(nlogn)或更低。
4. 第六天刷题总结与后续计划
今日完成题目:
- P3156 【深基15.例1】询问学号(数组基础)
- P1090 [NOIP2004 提高组] 合并果子(贪心算法)
- P1048 [NOIP2005 普及组] 采药(动态规划)
通过这三道题,我巩固了以下知识点:
- 大规模数据的快速IO处理方法
- 贪心算法的证明思路(局部最优→全局最优)
- 动态规划的空间优化技巧(滚动数组)
明日计划:
- 上午:图论基础(DFS/BFS实现)
- 下午:并查集与最小生成树
- 晚上:线段树模板题练习
个人体会:刷题时建议准备错题本,记录每道题的解题思路和易错点。我习惯用Markdown记录,例如:
markdown复制## P1090 合并果子
- 知识点:贪心、优先队列
- 易错点:注意ans要用long long
- 优化方向:双队列法将O(nlogn)→O(n)
这种结构化记录方式,三个月后复习时仍能快速回忆起解题要点。
