AcWing 276. I-区域
题目
给出 n 行 m 列矩阵,和一个参数 k,求 k 个格子组成的凸联通块最大权值和。(凸连通块就是形状是凸性的)。n < 30, m <
分析
此处凸包定义不严谨,可以直接理解成连续的若干行,每行的左端点列号先递减、后递增,右端点列号先递增、后递减。(这里的递增递减都是不严格的)
那么这样就可以从每一行入手,考虑每行选取的起点和终点。
①: 状态表示(经验)
首先定义左右两边递增状态用 0 表示,递减状态用 1 表示。
- 集合: 表示所有选完前 i 行,且一共选了 j 个格子,第 i 行选取的左边界是 L, 右边界是 R,左边界递增递减状态是 x,右边界递增递减状态是 y 的连通块的集合
- 属性:表示集合中所有方案格子权值和最大值。
②: 状态转移
对于每行的选取,左右边界的递增递减状态都有要求,那么如何在选取过程中满足这种要求?设计一个状态机:
也就是递减状态只能由递fdffd减推,而递增状态可以由递减、递增状态推。这样就满足了题目要求(先递减后递增)。右边界的自动机同理(先递增后递减)。
对于状态 (x, y):
假如为 (1, 0),只能由 (1, 0)转移,即 (1, 0) --> (1, 0)
(1, 0), (1, 1) --> (1, 1)
(0, 0), (1, 0) --> (0, 0)
(0, 1), (1,1), (0, 0), (1, 0) —> (0, 1)
而同时考虑左右边界 (L, R) 与上一行 (p, q) 的关系就有四种状态:
总结一下:
- 当 ,则:
,其中: - 当 ,则:
,其中:’ - 当 ,则:
,其中: - 当 ,则:
,其中:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll INF = 0x3f3f3f3f;
const int N = 16;
int n, m, k;
int w[N][N]; // 每个点的权值
int dp[N][N * N][N][N][2][2];
struct State {
int i, j, l, r, x, y;
} f[N][N * N][N][N][2][2]; // 保存状态从什么地方转移过来
int main() {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", &w[i][j]);
}
}
for (int i = 1; i <= n; i++)
for (int j = 0; j <= k; j++)
for (int l = 1; l <= m; l++)
for (int r = l; r <= m; r++) {
if (j < r - l + 1) continue;
// 左扩张,右扩张(1,0)
// 大括号为了变量名不干扰
{
auto &vd = dp[i][j][l][r][1][0];
auto &vf = f[i][j][l][r][1][0];
for (int p = l; p <= r; p++)
for (int q = p; q <= r; q++) {
int val = dp[i - 1][j - (r - l + 1)][p][q][1][0];
if (vd < val) {
vd = val;
// 记录路径
vf = {i - 1, j - (r - l + 1), p, q, 1, 0};
}
}
for (int u = l; u <= r; u++) vd += w[i][u];
}
// 左扩张,右收缩(1,1)
{
auto &vd = dp[i][j][l][r][1][1];
auto &vf = f[i][j][l][r][1][1];
for (int p = l; p <= r; p++)
for (int q = r; q <= m; q++)
for (int y = 0; y < 2; y++) {
int val = dp[i - 1][j - (r - l + 1)][p][q][1][y];
if (vd < val) {
vd = val;
// 记录路径
vf = {i - 1, j - (r - l + 1), p, q, 1, y};
}
}
for (int u = l; u <= r; u++) vd += w[i][u];
}
// 左收缩,右扩张(0,0)
{
auto &vd = dp[i][j][l][r][0][0];
auto &vf = f[i][j][l][r][0][0];
for (int p = 1; p <= l; p++)
for (int q = l; q <= r; q++)
for (int x = 0; x < 2; x++) {
int val = dp[i - 1][j - (r - l + 1)][p][q][x][0];
if (vd < val) {
vd = val;
// 记录路径
vf = {i - 1, j - (r - l + 1), p, q, x, 0};
}
}
for (int u = l; u <= r; u++) vd += w[i][u];
}
// 左扩张,右扩张(0,1)
{
auto &vd = dp[i][j][l][r][0][1];
auto &vf = f[i][j][l][r][0][1];
for (int p = 1; p <= l; p++)
for (int q = r; q <= m; q++)
for (int x = 0; x < 2; x++)
for (int y = 0; y < 2; y++) {
int val = dp[i - 1][j - (r - l + 1)][p][q][x][y];
if (vd < val) {
vd = val;
// 记录路径
vf = {i - 1, j - (r - l + 1), p, q, x, y};
}
}
for (int u = l; u <= r; u++) vd += w[i][u];
}
}
int ans = 0;
State state;
for (int i = 1; i <= n; i++)
for (int l = 1; l <= m; l++)
for (int r = l; r <= m; r++)
for (int x = 0; x < 2; x++)
for (int y = 0; y < 2; y++) {
int val = dp[i][k][l][r][x][y];
if (ans < val) {
ans = val;
state = {i, k, l, r, x, y};
}
}
printf("oil : %d\n", ans);
while(state.j) {
for (int i = state.l; i <= state.r; i++)
printf("%d %d\n", state.i, i);
state = f[state.i][state.j][state.l][state.r][state.x][state.y];
}
return 0;
}