这是一个典型的指派问题(Assignment Problem),属于组合优化领域。问题描述为:给定一个5×5的效益矩阵,需要选择5个数字,每个数字来自不同的行和列,使得这些数字的和最大。
这个问题可以抽象为二分图的最大权匹配问题:
数学表达式为:
max ΣΣ Aij·xij
s.t. Σxij = 1 ∀i (每行选一个)
Σxij = 1 ∀j (每列选一个)
xij ∈
c复制void backtrack(int row, int current_sum) {
if (row == N) {
if (current_sum > max_sum) {
max_sum = current_sum;
}
return;
}
for (int col = 0; col < N; col++) {
if (!used[col]) {
used[col] = 1;
backtrack(row + 1, current_sum + table[row][col]);
used[col] = 0;
}
}
}
这个基础版本会遍历所有可能的排列组合,时间复杂度为O(n!),对于n=5来说有120种可能,尚可接受。
为提高效率,我们引入预估函数进行剪枝:
c复制int estimate_max(int row) {
int est = 0;
for (int r = row; r < N; r++) {
int row_max = 0;
for (int c = 0; c < N; c++) {
if (!used[c] && table[r][c] > row_max) {
row_max = table[r][c];
}
}
est += row_max;
}
return est;
}
在回溯过程中加入剪枝判断:
c复制if (current_sum + estimate_max(row) <= max_sum) {
return;
}
这个剪枝策略利用贪心思想预估剩余行可能获得的最大收益,如果当前路径即使加上最优预估也无法超越已知最大值,则提前终止该路径的搜索。
对于n=5的规模,该算法完全可行。若n增大,应考虑更高效的算法如匈牙利算法(O(n³))。
c复制#include <stdio.h>
#include <limits.h>
#define N 5
int table[N][N];
int used[N];
int max_sum;
int estimate_max(int row) {
int est = 0;
for (int r = row; r < N; r++) {
int row_max = 0;
for (int c = 0; c < N; c++) {
if (!used[c] && table[r][c] > row_max) {
row_max = table[r][c];
}
}
est += row_max;
}
return est;
}
void backtrack(int row, int current_sum) {
if (row == N) {
if (current_sum > max_sum) {
max_sum = current_sum;
}
return;
}
if (current_sum + estimate_max(row) <= max_sum) {
return;
}
for (int col = 0; col < N; col++) {
if (!used[col]) {
used[col] = 1;
backtrack(row + 1, current_sum + table[row][col]);
used[col] = 0;
}
}
}
int main() {
while (scanf("%d", &table[0][0]) != EOF) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (i == 0 && j == 0) continue;
scanf("%d", &table[i][j]);
}
}
for (int i = 0; i < N; i++) used[i] = 0;
max_sum = 0;
backtrack(0, 0);
printf("%d\n", max_sum);
}
return 0;
}
这个阵列的填充规律类似于螺旋矩阵,但有独特特点:
根据n的范围预先确定最小能容纳所有数字的网格尺寸:
c复制if (n == 1) {
rows = cols = 1;
} else if (n <= 4) {
rows = 2; cols = 2;
} else if (n <= 9) {
rows = cols = 3;
}
// 其他情况类似...
使用状态机模式控制填充方向:
c复制int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; // 下、右、上、左
int step_len = 1;
int d = 0; // 当前方向
int step_count = 0;
int dir_count = 0;
填充逻辑:
c复制while (num <= n) {
// 尝试沿当前方向移动
if (遇到边界或已填充) {
// 改变方向
// 更新步长(每两次方向改变)
}
// 填充数字
// 更新步数计数
}
c复制// 找出有数字的最小/最大行列
for (遍历所有格子) {
if (arr[i][j] != 0) {
更新min_r, max_r, min_c, max_c;
}
}
c复制// 确定每列宽度(1位或2位)
for (每列) {
if (该列有两位数) {
设置列宽为2;
}
}
c复制#include <stdio.h>
#include <string.h>
int main() {
int n;
int first = 1;
while (scanf("%d", &n) == 1) {
if (!first) printf("\n");
first = 0;
// 网格大小确定(略)
// 填充过程(略)
// 输出处理(略)
}
return 0;
}
设三种饲料的份数为t,u,v,目标饲料份数为w,则有:
code复制t*a1 + u*a2 + v*a3 = w*x
t*b1 + u*b2 + v*b3 = w*y
t*c1 + u*c2 + v*c3 = w*z
这是一个齐次线性方程组,我们需要找到非负整数解。
由于变量范围小(<100),可以采用三重循环:
c复制for (int t = 0; t < 100; t++) {
for (int u = 0; u < 100; u++) {
for (int v = 0; v < 100; v++) {
// 计算总量
// 检查比例关系
// 记录最优解
}
}
}
c复制int w = -1;
if (x != 0 && total_a % x == 0) {
w = total_a / x;
// 检查其他分量...
}
c复制#include <stdio.h>
int main() {
int x, y, z;
scanf("%d %d %d", &x, &y, &z);
int a[3], b[3], c[3];
for (int i = 0; i < 3; i++) {
scanf("%d %d %d", &a[i], &b[i], &c[i]);
}
int min_sum = 300;
int best_t = -1, best_u = -1, best_v = -1, best_w = -1;
for (int t = 0; t < 100; t++) {
for (int u = 0; u < 100; u++) {
for (int v = 0; v < 100; v++) {
int total_a = t*a[0] + u*a[1] + v*a[2];
int total_b = t*b[0] + u*b[1] + v*b[2];
int total_c = t*c[0] + u*c[1] + v*c[2];
// 计算和验证w(略)
if (满足比例条件) {
int sum = t + u + v;
if (sum < min_sum) {
// 更新最优解
}
}
}
}
}
if (best_t == -1) {
printf("NONE\n");
} else {
printf("%d %d %d %d\n", best_t, best_u, best_v, best_w);
}
return 0;
}
这类指派问题在现实中应用广泛:
对于更大规模的问题,建议使用:
可以尝试以下变体:
更高效的解法可以考虑:
提示:在实际编程竞赛中,对于小范围约束的问题,暴力枚举法往往是可行且不易出错的选择。但在生产环境中,应考虑更高效的算法。