1. 海军节礼炮问题解析与实现
1.1 问题建模与分析
海军节礼炮问题本质上是一个时间序列的重叠计算问题。我们需要计算在特定时间段内,三个不同周期事件(A舰每5秒、B舰每6秒、C舰每7秒鸣放一次)的所有发生时刻,然后统计这些时刻的去重数量。
关键观察点:
- 各舰鸣放持续时间不同:A舰持续5×20=100秒,B舰持续6×20=120秒,C舰持续7×20=140秒
- 需要计算的时间范围是0到140秒(以C舰结束时间为准)
- 每次鸣放都是瞬时完成的(题目假设时间掌握非常准确)
1.2 解法一:时间点遍历法
c复制#include <stdio.h>
int main() {
int count = 0;
// 遍历从0到140秒的每个时间点
for(int now = 0; now <= 140; now++) {
// 检查当前时间点是否有舰鸣炮
int a_fire = (now % 5 == 0) && (now <= 100); // A舰在有效时间内每5秒
int b_fire = (now % 6 == 0) && (now <= 120); // B舰在有效时间内每6秒
int c_fire = (now % 7 == 0); // C舰全程每7秒
if(a_fire || b_fire || c_fire) {
count++;
}
}
printf("n = %d\n", count);
return 0;
}
注意事项:这种方法虽然直观,但效率不高,因为需要遍历每个时间点。在实际工程中,对于大时间范围的问题可能需要优化。
1.3 解法二:集合排除法
更高效的解法是利用数学集合原理:总次数 = A次数 + B次数 + C次数 - AB重合 - AC重合 - BC重合 + ABC重合
c复制#include <stdio.h>
// 计算最大公约数
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
// 计算最小公倍数
int lcm(int a, int b) {
return a * b / gcd(a, b);
}
int main() {
int a_total = 21;
int b_total = 21;
int c_total = 21;
// 计算两两重合次数
int ab = (5*20)/lcm(5,6) + 1;
int ac = (5*20)/lcm(5,7) + 1;
int bc = (6*20)/lcm(6,7) + 1;
// 计算三者重合次数
int abc = (5*20)/lcm(lcm(5,6),7) + 1;
int total = a_total + b_total + c_total - ab - ac - bc + abc;
printf("n = %d\n", total);
return 0;
}
实操心得:集合法虽然数学上更优雅,但实现复杂度较高。对于初学者,建议先掌握时间点遍历法,等熟悉了基础编程概念后再学习更高级的算法。
2. 星期显示程序的实现与优化
2.1 基础实现方案
最直接的实现方式是使用switch-case结构:
c复制#include <stdio.h>
int main() {
int day;
printf("Input a single numeral:");
scanf("%d", &day);
switch(day) {
case 0: printf("Sunday\n"); break;
case 1: printf("Monday\n"); break;
case 2: printf("Tuesday\n"); break;
case 3: printf("Wednesday\n"); break;
case 4: printf("Thursday\n"); break;
case 5: printf("Friday\n"); break;
case 6: printf("Saturday\n"); break;
default: printf("%d\n", day);
}
return 0;
}
2.2 使用数组的优化方案
更简洁的实现是使用字符串数组:
c复制#include <stdio.h>
int main() {
const char *days[] = {
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"
};
int day;
printf("Input a single numeral:");
scanf("%d", &day);
if(day >= 0 && day <= 6) {
printf("%s\n", days[day]);
} else {
printf("%d\n", day);
}
return 0;
}
编程技巧:使用字符串数组可以减少代码量,提高可读性。const修饰符确保字符串常量不会被意外修改。
2.3 输入验证增强
在实际应用中,应该增加输入验证:
c复制#include <stdio.h>
#include <ctype.h>
int main() {
char input[100];
int day;
printf("Input a single numeral:");
fgets(input, sizeof(input), stdin);
if(sscanf(input, "%d", &day) != 1) {
printf("Invalid input\n");
return 1;
}
const char *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"};
if(day >= 0 && day <= 6) {
printf("%s\n", days[day]);
} else {
printf("%d\n", day);
}
return 0;
}
注意事项:使用fgets+sscanf组合比直接使用scanf更安全,可以避免缓冲区溢出问题。
3. 快递费用计算的完整实现
3.1 问题分析与建模
快递费用计算需要考虑:
- 区域划分(0-4区)
- 重量计算(首重+续重,向上取整)
- 不同区域的资费标准
3.2 基础实现代码
c复制#include <stdio.h>
#include <math.h>
int main() {
int area;
float weight;
printf("请输入区域和重量(格式:区域,重量):");
if(scanf("%d,%f", &area, &weight) != 2 || weight <= 0) {
printf("输入格式错误\n");
return 1;
}
if(area < 0 || area > 4) {
printf("Error in Area\n");
return 0;
}
// 计算续重(向上取整)
float extra_weight = ceil(weight - 1.0);
if(extra_weight < 0) extra_weight = 0;
float price;
switch(area) {
case 0: // 同城
price = 10.0 + extra_weight * 3.0;
break;
case 1: // 江浙
price = 10.0 + extra_weight * 4.0;
break;
case 2: // 其他区域2区
price = 15.0 + extra_weight * 5.0;
break;
case 3: // 其他区域3区
price = 15.0 + extra_weight * 6.5;
break;
case 4: // 其他区域4区
price = 15.0 + extra_weight * 10.0;
break;
}
printf("Price: %5.2f\n", price);
return 0;
}
3.3 代码优化与重构
更清晰的实现方式是将资费规则定义为结构体:
c复制#include <stdio.h>
#include <math.h>
typedef struct {
float base_price;
float extra_price;
} ShippingRate;
int main() {
// 定义各区域资费标准
ShippingRate rates[] = {
{10.0, 3.0}, // 区域0
{10.0, 4.0}, // 区域1
{15.0, 5.0}, // 区域2
{15.0, 6.5}, // 区域3
{15.0, 10.0} // 区域4
};
int area;
float weight;
printf("请输入区域和重量(格式:区域,重量):");
if(scanf("%d,%f", &area, &weight) != 2 || weight <= 0) {
printf("输入格式错误\n");
return 1;
}
if(area < 0 || area > 4) {
printf("Error in Area\n");
return 0;
}
float extra_weight = ceil(weight - 1.0);
if(extra_weight < 0) extra_weight = 0;
float price = rates[area].base_price + extra_weight * rates[area].extra_price;
printf("Price: %5.2f\n", price);
return 0;
}
编程技巧:使用结构体数组存储资费标准,可以使代码更易维护,当资费调整时只需修改结构体数组即可。
4. 三角形判断的全面解析
4.1 三角形基本判定条件
根据三角形不等式,三条边a、b、c能构成三角形的条件是:
- a + b > c
- a + c > b
- b + c > a
- 所有边长必须为正数
4.2 特殊三角形判断
特殊三角形包括:
- 等腰三角形:至少两边相等
- 等边三角形:三边相等(题目要求归为等腰)
- 直角三角形:满足勾股定理
- 等腰直角三角形:既是等腰又是直角
4.3 完整实现代码
c复制#include <stdio.h>
#include <math.h>
#define EPSILON 0.1 // 浮点比较的误差范围
int main() {
float a, b, c;
printf("Input a,b,c:");
scanf("%f,%f,%f", &a, &b, &c);
// 检查是否为有效三角形
if(a <= 0 || b <= 0 || c <= 0 ||
a + b <= c || a + c <= b || b + c <= a) {
printf("不是三角形\n");
return 0;
}
// 判断是否为等腰
int is_isosceles = (fabs(a - b) < EPSILON) ||
(fabs(b - c) < EPSILON) ||
(fabs(c - a) < EPSILON);
// 判断是否为直角
int is_right = 0;
// 对边排序,c为最大边
if(a > b && a > c) { float t = a; a = c; c = t; }
if(b > a && b > c) { float t = b; b = c; c = t; }
if(fabs(a*a + b*b - c*c) < EPSILON) {
is_right = 1;
}
// 输出判断结果
if(is_right && is_isosceles) {
printf("等腰直角三角形\n");
} else if(is_right) {
printf("直角三角形\n");
} else if(is_isosceles) {
printf("等腰三角形\n");
} else {
printf("一般三角形\n");
}
return 0;
}
4.4 浮点数比较的注意事项
在判断浮点数相等时,不能直接用==运算符,而应该使用误差范围比较:
c复制// 不推荐
if(a == b) { ... }
// 推荐
if(fabs(a - b) < EPSILON) { ... }
常见错误:忘记处理浮点精度问题,导致判断结果不准确。特别是在勾股定理判断时,浮点误差可能导致本应是直角三角形的被误判。
5. 回文素数的算法优化
5.1 回文素数定义
回文素数是指既是素数又是回文数的数。特点:
- 素数:只能被1和自身整除
- 回文数:正读反读都相同的数
5.2 基础实现算法
c复制#include <stdio.h>
#include <math.h>
int is_prime(int n) {
if(n < 2) return 0;
if(n == 2) return 1;
if(n % 2 == 0) return 0;
for(int i = 3; i <= sqrt(n); i += 2) {
if(n % i == 0) return 0;
}
return 1;
}
int is_palindrome(int n) {
if(n < 10) return 1; // 一位数都是回文
int original = n;
int reversed = 0;
while(n > 0) {
reversed = reversed * 10 + n % 10;
n /= 10;
}
return original == reversed;
}
int main() {
printf("Following are palindrome primes not greater than 1000:\n");
for(int n = 2; n <= 1000; n++) {
if(is_prime(n) && is_palindrome(n)) {
printf("%d\t", n);
}
}
printf("\n");
return 0;
}
5.3 算法优化策略
- 素数筛法预先生成素数表
- 只检查奇数(除了2,所有素数都是奇数)
- 优化回文数检查
优化后的实现:
c复制#include <stdio.h>
#include <string.h>
#define MAX 1000
void sieve(int primes[]) {
memset(primes, 1, (MAX+1)*sizeof(int));
primes[0] = primes[1] = 0;
for(int i = 2; i*i <= MAX; i++) {
if(primes[i]) {
for(int j = i*i; j <= MAX; j += i) {
primes[j] = 0;
}
}
}
}
int is_palindrome(int n) {
char str[10];
sprintf(str, "%d", n);
int len = strlen(str);
for(int i = 0; i < len/2; i++) {
if(str[i] != str[len-1-i]) return 0;
}
return 1;
}
int main() {
int primes[MAX+1];
sieve(primes);
printf("Following are palindrome primes not greater than 1000:\n");
// 单独处理2
if(is_palindrome(2)) printf("2\t");
// 检查奇数
for(int n = 3; n <= 1000; n += 2) {
if(primes[n] && is_palindrome(n)) {
printf("%d\t", n);
}
}
printf("\n");
return 0;
}
性能对比:基础算法时间复杂度O(n√n),优化后的筛法算法时间复杂度O(n log log n),在大数据量时优势明显。
5.4 回文数检查的多种实现
方法一:数字反转法
c复制int is_palindrome(int n) {
int original = n, reversed = 0;
while(n > 0) {
reversed = reversed * 10 + n % 10;
n /= 10;
}
return original == reversed;
}
方法二:字符串比较法
c复制int is_palindrome(int n) {
char str[20];
sprintf(str, "%d", n);
int len = strlen(str);
for(int i = 0; i < len/2; i++) {
if(str[i] != str[len-1-i]) return 0;
}
return 1;
}
方法三:数字位比较法
c复制int is_palindrome(int n) {
if(n < 10) return 1;
int digits[10];
int count = 0;
while(n > 0) {
digits[count++] = n % 10;
n /= 10;
}
for(int i = 0; i < count/2; i++) {
if(digits[i] != digits[count-1-i]) return 0;
}
return 1;
}
选择建议:数字反转法最简单高效,但在处理极大数时可能溢出;字符串法通用性强但稍慢;数字位法折中但代码稍复杂。