給定學生-課程成績矩陣(0 代表未選修),對每門課程計算一個 delta,使「選修該課程的學生,成績各加 delta 後的平均分」等於「這些學生所有課程平均分的平均值」。輸出每門課程的 delta。
設:
S,人數 k = |S|。i ∈ S 在該課程原始成績 y_i。i 所有選修課程的平均分 avg_i = (Σ_j score_{i,j}) / cnt_i。(Σ_{i∈S} (y_i + delta)) / k = (Σ_{i∈S} avg_i) / k解得:
delta = (Σ avg_i - Σ y_i) / k
= (sum_avg - total_course_score) / count_selected
student_avg[i],避免重複迴圈(原始碼中嵌套循環導致 O(S×C²))。student_avg 與該課原始成績。delta。
#include
#define MAX_STU 20
#define MAX_CRS 10
int main(void) {
int stu_n, crs_n;
if (scanf("%d %d", &stu_n, &crs_n) != 2) return 0;
char crs_name[MAX_CRS][16];
for (int i = 0; i < crs_n; i++) scanf("%s", crs_name[i]);
int score[MAX_STU][MAX_CRS];
for (int i = 0; i < stu_n; i++)
for (int j = 0; j < crs_n; j++)
scanf("%d", &score[i][j]);
// 步驟 1:預計算每位學生平均分
double stu_avg[MAX_STU] = {0};
for (int i = 0; i < stu_n; i++) {
int sum = 0, cnt = 0;
for (int j = 0; j < crs_n; j++) {
if (score[i][j] != 0) { sum += score[i][j]; cnt++; }
}
stu_avg[i] = cnt ? (double)sum / cnt : 0.0;
}
// 步驟 2:逐門課程計算 delta
for (int c = 0; c < crs_n; c++) {
double sum_avg = 0; // Σ avg_i
int total_score = 0; // Σ y_i
int cnt = 0; // k
for (int s = 0; s < stu_n; s++) {
if (score[s][c] != 0) {
sum_avg += stu_avg[s];
total_score += score[s][c];
cnt++;
}
}
int delta = cnt ? (int)((sum_avg - total_score) / cnt) : 0;
printf("%s %d\n", crs_name[c], delta);
}
return 0;
}
| 版本 | 時間複雜度 | 關鍵優化 |
|---|---|---|
| 原始嵌套循環 | O(Stu × Crs²) | 每門課重複計算學生平均分 |
| 預計算優化 | O(Stu × Crs) | 學生平均分僅算一次,查表 O(1) |
輸入:
3 2
Math Physics
80 90
70 0
0 60
學生平均: (85, 70, 60)
Math: 選修生 0,1 → avg=(85+70)/2=77.5, 總分=150 → delta=(155-150)/2=2
Physics: 選修生 0,2 → avg=(85+60)/2=72.5, 總分=150 → delta=(145-150)/2=-2
輸出:
Math 2
Physics -2
「分步實現、打基礎、再優化」是解題通法:先寫出正確但可能低效的版本,識別重複計算熱點,引入中間陣列/快取消除冗餘,最終達到最優複雜度。此題核心在於「預計算學生平均分」將 O(Crs²) 降為 O(Crs)。