题意为:
有一个长方体形状的废料堆,由A*B*C个废料块组成,每一个废料块都有一个价值,可正可负。现在要在这个长方体上选择一个子长方形,使组成这个子长方形的废料块的价值之和最大。 1<= A, B, C <=20
分析:
如果采用暴力法枚举起点(x,y,z),并且枚举长宽高,则需要n^6*n^3的时间复杂度,显示会超时。因此我们记录每一个点与(1,1,1)坐标组成的长方体的值。在运算中通过枚举 x1,x2,y1,y2,x1为长方体的起始x坐标,x2为长方体的结束x坐标。y1为长方体的起始y坐标,y2为长方体的结束y坐标。并且枚举z的坐标位置,通过记录最大值和状态值进行比较。最终将时间复杂度压缩到n^5.
一般情况下可通过记录有意义的状态值来减少时间复杂度。
题解代码为:
code1:
#include<cstdio>
#include<map>
#include<string.h>
#include<iostream>
using namespace std;
const int maxn = 22;
long long cube[maxn][maxn][maxn];
void binary(int d, int& b0, int& b1, int& b2) {
b0 = d % 2; d /= 2;
b1 = d % 2; d /= 2;
b2 = d % 2;
}
int sign(int d) {
if(d%2) return 1;
return -1;
}
long long sum(int a1, int a2, int b1, int b2, int c1, int c2) {
long long ans = 0;
int dx = a2 - a1 + 1,
dy = b2 - b1 + 1,
dz = c2 - c1 + 1;
for(int i = 0; i < 8; i++) {
int x0, x1, x2;
binary(i, x0, x1, x2);
ans -= sign(x0+x1+x2) * cube[a2-dx*x0][b2-dy*x1][c2-dz*x2];
}
return ans;
}
int main() {
int A, B, C;
int t;
scanf("%d", &t);
while(t--) {
scanf("%d%d%d", &A, &B, &C);
for(int a = 1; a <= A; a++) {
for(int b = 1; b <= B; b++) {
for(int c = 1; c <= C; c++) scanf("%lld", &cube[a][b][c]);
}
}
for(int a = 1; a <= A; a++) {
for(int b = 1; b <= B; b++) {
for(int c = 1; c <= C; c++) {
for(int d = 1; d < 8; d++) {
int b0, b1, b2;
binary(d, b0, b1, b2);
cube[a][b][c] += sign(b0+b1+b2) * cube[a-b0][b-b1][c-b2];
}
}
}
}
long long ans = cube[1][1][1];
for(int a1 = 1; a1 <= A; a1++) {
for(int a2 = a1; a2 <= A; a2++) {
for(int b1 = 1; b1 <= B; b1++) {
for(int b2 = b1; b2 <= B; b2++) {
long long tmin = 0;
for(int z = 1; z <= C; z++) {
long long tsum = sum(a1, a2, b1, b2, 1, z);
ans = max(ans, tsum - tmin);
tmin = min(tmin, tsum);
}
}
}
}
}
printf("%lld\n", ans);
if(t) printf("\n");
}
return 0;
}
code2:
第二种方法只记录了c坐标方向的前缀和,因此最后计算和时,需要多加入一次循环累加。
#include<cstdio>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn = 22;
long long cube[maxn][maxn][maxn];
int main() {
int A, B, C;
int t;
scanf("%d", &t);
memset(cube, 0, sizeof(cube));
while(t--) {
scanf("%d%d%d", &A, &B, &C);
for(int a = 1; a <= A; a++) {
for(int b = 1; b <= B; b++) {
for(int c = 1; c <= C; c++) {
scanf("%lld", &cube[a][b][c]);
cube[a][b][c] += cube[a][b][c-1];
}
}
}
long long ans;
ans = cube[1][1][1];
for(int b1 = 1; b1 <= B; b1++) {
for(int b2 = b1; b2 <= B; b2++) {
for(int c1 = 0; c1 <= C; c1++) {
for(int c2 = c1 + 1; c2 <= C; c2++) {
long long cmax;
for(int a = 1; a <= A; a++) {
long long sum = 0;
for(int b = b1; b <= b2; b++) sum = sum + cube[a][b][c2] - cube[a][b][c1];
cmax = (a==1) ? sum : max(cmax+sum, sum);
ans = max(ans, cmax);
}
}
}
}
}
printf("%lld\n", ans);
if(t) printf("\n");
}
return 0;
}
上述题目的一个一维形式就是求一个序列的最大连续数之和。
1. 一个整数(可正可负)序列,有A个元素。在整数序列中选择一个子序列,使得子序列的和最大。
分析:通过记录前缀和,并不断记录和更新状态。
代码为:
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100 + 10;
int A[maxn], S[maxn];
int main() {
int n;
while(scanf("%d", &n) != EOF && n) {
for(int a = 1; a <= n; a++) {
scanf("%d", &A[a]);
S[a] = S[a-1] + A[a];
}
int ans, cmax;
ans = cmax = A[1];
for(int a = 2; a <= n; a++) {
cmax = max(cmax+A[a], A[a]);
ans = max(ans, cmax);
}
printf("%d\n", ans);
}
return 0;
}
上述题目的二维形式如下图所示。
2. 长方形形状的物体,由A*B个块组成。每个块都有一个价值(可正可负的整数)在长方形中选择一个子长方形,使得子长方形的块的价值之和最大。
代码为:
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100 + 10;
int A[maxn][maxn];
int main() {
int m,n;
while(scanf("%d%d", &m, &n) != EOF && m) {
for(int a = 1; a <= m; a++) {
for(int b = 0; b < n; b++) {
scanf("%d", &A[a][b]);
A[a][b] += A[a-1][b];
}
}
int ans, tMax;
ans = A[1][0];
for(int a = 0; a <= m; a++) {
for(int b = a + 1; b <= m; b++) {
tMax = A[b][0] - A[a][0];
for(int c = 1; c < n; c++) {
tMax = max(tMax+A[b][c]-A[a][c], A[b][c]-A[a][c]);
ans = max(ans, tMax);
}
}
}
printf("%d\n", ans);
}
return 0;
}
此题也可以通过记录以(x,y)坐标结尾的前缀和来解决,这样具有更高的通用性。