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;
}