[题目][APIO2009][蓝桥杯ALGO-44] 采油区域
一、题目
0、题目链接
http://lx.lanqiao.cn/problem.page?gpid=T104(需要登录且需要 VIP 账户)
1、问题描述
Siruseri政府决定将石油资源丰富的Navalur省的土地拍卖给私人承包商以建立油井。被拍卖的整块土地为一个矩形区域,被划分为M×N个小块。
Siruseri地质调查局有关于Navalur土地石油储量的估测数据。这些数据表示为M×N个非负整数,即对每一小块土地石油储量的估计值。
为了避免出现垄断,政府规定每一个承包商只能承包一个由K×K块相连的土地构成的正方形区域。
AoE石油联合公司由三个承包商组成,他们想选择三块互不相交的K×K的区域使得总的收益最大。
AoE公司雇佣你来写一个程序,帮助计算出他们可以承包的区域的石油储量之和的最大值。
2、输入格式
输入第一行包含三个整数M, N, K,其中M和N是矩形区域的行数和列数,K是每一个承包商承包的正方形的大小(边长的块数)。接下来M行,每行有N个非负整数表示这一行每一小块土地的石油储量的估计值。
3、输出格式
输出只包含一个整数,表示AoE公司可以承包的区域的石油储量之和的最大值。
4、样例输入
9 9 3
1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1
1 8 8 8 8 8 1 1 1
1 8 8 8 8 8 1 1 1
1 8 8 8 8 8 1 1 1
1 1 1 1 8 8 8 1 1
1 1 1 1 1 1 8 8 8
1 1 1 1 1 1 9 9 9
1 1 1 1 1 1 9 9 9
5、样例输出
208
6、数据规模
数据保证K≤M且K≤N并且至少有三个K×K的互不相交的正方形区域。其中30%的输入数据,M, N≤ 12。所有的输入数据, M, N≤ 1500。每一小块土地的石油储量的估计值是非负整数且≤ 500。
二、分析与思路
APIO 的题还是上升了个档次。
首先需要二维前缀和是显然的,然后最开始的想法是动态规划,然而 O(n ^ 2 * m ^ 2 * k) 的时间复杂度直接劝退。n, m <= 1500 说明最多为 O(n * m),只能通过预处理一些数据来降低复杂度。
由于采油区域只需要选择 3 个,是个固定值,可以从这一点入手 ——
将整个区域分为三个部分,每块 k * k 的采油区分别从这三个部分中选取。将矩形拆分成三个矩形有如下 6 种形式:
对于第 ① 到 ④ 种情况,我们可以通过枚举矩形中的 n * m(考虑到可行性,实际上为 (n - 2 * k) * (m - 2 * k))个点表示出所有方案;对于第 ⑤ 和 ⑥ 种情况,稍后再讨论。
对于点的枚举的时间复杂度已经达到了可接受的最大数量级,剩下的计算只能考虑 O(1) 得到,我们可以利用预处理的二维前缀和。
假设 mp[i][j] 表示坐标为 (i, j) 的区域的石油储量,s[i][j] 表示以 (1, 1) 为左上角,(i, j) 为右下角的矩形的石油储量总和,即二维前缀和,t[i][j] 表示以 (i - k, j - k) 为左上角,(i, j) 为右下角的矩形的石油储量总和,这样我们先得到了整个区域的所有 k * k 的区域的石油储量和。实际上数组 mp 和 s 的值已经不会再被使用了,那么上述的运算可以直接覆盖在 mp 上。
我们将区域分成三个部分后,已知的信息是分割点 (x, y),以及过该点与两边垂直的直线与两边的交点 (x, 1), (x, m), (1, y), (n, y),也就是说我们只能通过这些数据直接求得每个部分的 k * k 的石油储量和的最大值并相加。拿第 ① 种情况为例,我们需要求得上面部分的该最大值,也就是说我们需要事先知道所有的 (1, 1) 到 (x, m) 的矩形的该最大值,不难想到可以通过 mp 数组预处理出来 —— 假设 a[i][j] 表示以 (1, 1) 为左上角,(i, j) 为右下角的所有 k * k 区域的石油储量和的最大值,通过一个简单的递推就能得到。
然而,并不是所有区域可以通过其右下角的坐标就能正确划分,对于第 ③ 种情况,其右侧区域并不能通过任何 “左上角 (1, 1),右下角 (i, j)” 的形式来表示,但因为其右上角(右下角也是可以的)为整个矩形的顶点,则可以通过 “右上角 (1, m),左下角 (i, j)” 的形式来表示,那么同理我们可以假设 b[i][j] 表示以 (1, m) 为右上角,(i, j) 为左下角的所有 k * k 区域的石油储量和的最大值,求解方式也是同理。
除了左上和右上,同样可以求出左下和右下两种情况,分别用 c[i][j] 和 d[i][j] 表示。这个过程同样是 O((n - k) * (m - k)) 级的时间复杂度,进而可以很快得到前四种情况。
而对于第 ⑤ 和 ⑥ 种情况,则略有不同。因为上述的预处理都是建立在分出来的部分以整个矩形的顶点为角的前提下,而这两种情况存在被分隔在中间的一块区域,并不能被上述四种形式来表示,但其实有更为简单的方法 —— 对于中间的部分,其宽度大小其实是没意义的,我们只是需要在中间选择一个 k * k 的区域,那么它的宽度是 k,还是 k + 1, k + 2, ...,我们的选择都是等价的,那么我们为何不直接假设中间部分的宽度就是 k 呢?通过两条分割线的平移,我们完全可以得到任何方案的最优解。固定了宽度后,分割线的枚举也就成了 O(n) 级别,再补上对另一维的枚举即可。
(有些地方讲得很模糊,将来再补充)
三、代码
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 #define MAXN 1505 5 6 int n, m, k, o, mp[MAXN][MAXN], ans, ans1, ans2, ans3, ans4; 7 int a[MAXN][MAXN], b[MAXN][MAXN], c[MAXN][MAXN], d[MAXN][MAXN]; 8 9 10 int main() { 11 cin >> n >> m >> k; 12 for (int i = 1; i <= n; i++) 13 for (int j = 1; j <= m; j++) 14 cin >> o, mp[i][j] = mp[i - 1][j] + mp[i][j - 1] - mp[i - 1][j - 1] + o; 15 for (int i = n; i >= k; i--) 16 for (int j = m; j >= k; j--) 17 mp[i][j] -= mp[i - k][j] + mp[i][j - k] - mp[i - k][j - k]; 18 for (int i = k; i <= n; i++) 19 for (int j = k; j <= m; j++) 20 a[i][j] = max(mp[i][j], max(a[i - 1][j], a[i][j - 1])); 21 for (int i = k; i <= n; i++) 22 for (int j = m; j >= k; j--) 23 b[i][j] = max(mp[i][j], max(b[i - 1][j], b[i][j + 1])); 24 for (int i = n; i >= k; i--) 25 for (int j = k; j <= m; j++) 26 c[i][j] = max(mp[i][j], max(c[i + 1][j], c[i][j - 1])); 27 for (int i = n; i >= k; i--) 28 for (int j = m; j >= k; j--) 29 d[i][j] = max(mp[i][j], max(d[i + 1][j], d[i][j + 1])); 30 for (int i = k; i <= n - k; i++) 31 for (int j = k; j <= n - k; j++) { 32 ans1 = a[i][m] + c[i + k][j] + d[i + k][j + k], ans2 = a[i][j] + b[i][j + k] + c[i + k][m]; 33 ans3 = a[n][j] + b[i][j + k] + d[i + k][j + k], ans4 = a[i][j] + b[n][j + k] + c[i + k][j]; 34 ans = max(ans, max(max(ans1, ans2), max(ans3, ans4))); 35 } 36 for (int i = k; i <= n; i++) 37 for (int j = 2 * k; j <= m - k; j++) 38 ans = max(ans, a[n][j - k] + b[n][j + k] + mp[i][j]); 39 for (int i = 2 * k; i <= n - k; i++) 40 for (int j = k; j <= m; j++) 41 ans = max(ans, a[i - k][m] + c[i + k][m] + mp[i][j]); 42 cout << ans; 43 return 0; 44 }