2007年10月USACO白银组的这道题目《Bessie's Secret Pasture》看似简单,实则蕴含了组合数学和暴力枚举的经典思想。题目描述了一个有趣的场景:农场主John有无限多块正方形牧草,Bessie需要从中选取四块来填满她牧场上的N个格子。
关键点在于理解题目要求:我们需要找出所有四个非负整数的平方和等于N的组合。这里的"组合"指的是有序元组,即(1,2,3,4)和(4,3,2,1)被视为不同的方案。
最直观的解法就是四重循环暴力枚举所有可能的组合:
cpp复制for (int i=0; i*i<=n; i++) {
for (int j=0; j*j<=n; j++) {
for (int k=0; k*k<=n; k++) {
for (int l=0; l*l<=n; l++) {
if (i*i + j*j + k*k + l*l == n) {
ans++;
}
}
}
}
}
这种方法的优点是简单直接,容易理解和实现。但缺点也很明显:时间复杂度为O(n²),当n较大时效率会很低。
我们可以从几个方面进行优化:
循环边界优化:每层循环只需要遍历到√n即可,因为超过√n的数的平方已经大于n了。
提前终止:在内层循环中,如果前三项的平方和已经大于n,可以直接跳过当前循环。
记忆化搜索:可以预先计算所有可能的平方数,然后使用动态规划或记忆化搜索来减少重复计算。
cpp复制#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n, ans = 0;
cin >> n;
int max_val = sqrt(n) + 1; // 确定最大可能的边长
for (int a=0; a<=max_val; a++) {
for (int b=0; b<=max_val; b++) {
for (int c=0; c<=max_val; c++) {
for (int d=0; d<=max_val; d++) {
if (a*a + b*b + c*c + d*d == n) {
ans++;
}
}
}
}
}
cout << ans << endl;
return 0;
}
cpp复制#include <iostream>
#include <cmath>
using namespace std;
int main() {
int n, ans = 0;
cin >> n;
int max_val = sqrt(n) + 1;
for (int a=0; a<=max_val; a++) {
int sum_a = a*a;
if (sum_a > n) break;
for (int b=0; b<=max_val; b++) {
int sum_ab = sum_a + b*b;
if (sum_ab > n) break;
for (int c=0; c<=max_val; c++) {
int sum_abc = sum_ab + c*c;
if (sum_abc > n) break;
for (int d=0; d<=max_val; d++) {
int total = sum_abc + d*d;
if (total == n) {
ans++;
} else if (total > n) {
break;
}
}
}
}
}
cout << ans << endl;
return 0;
}
这道题目实际上与数学中的"四平方和定理"有关。该定理指出:任何自然数都可以表示为不超过四个整数的平方和。虽然我们的题目限定正好是四个数,但这个定理为我们提供了理论支持。
原始的四重循环时间复杂度为O(n²),经过优化后,最坏情况下仍然是O(n²),但实际运行时会快很多,因为有很多提前终止的情况。
对于更大的n值(比如n≤10^6),我们可以考虑以下优化:
这道题目虽然简单,但体现了算法竞赛中的几个重要思想:
类似的题目在竞赛中很常见,比如:
在实际编程竞赛中,这类题目有几点经验值得注意:
我在最初解决这个问题时,犯过一个错误:没有考虑循环边界,导致程序运行时间过长。后来通过添加sqrt(n)的限制,性能得到了显著提升。这也提醒我们,即使是暴力解法,也要注意基本的优化。