初见安~本题解参考:lyd的《进阶》。
传送门为:入门OJ P4859,如原题那样输出方案……我的代码爆内存了QAQ暂时也想不到如何优化……但是在Acwing上,内存128MB是可以过的,所以也贴出来了【顺便求教如何把内存控制在64MBQwQ】
Description
在 N*M 的矩阵中,每个格子有一个权值,要求寻找一个包含 K 个格子的凸连通块(连通块中间没有空缺,并且轮
廓是凸的,如右图所示),使这个连通块中的格子的权值和最大。求出这个最大的权值和.N,M≤15,K≤225。
Input
第一行给出N,M,K,接下来给出这个矩阵
Output
如题
Sample Input
5 5 12
7 1 0 1 5
6 8 0 4 10
6 6 9 4 6
9 2 9 1 0
8 0 0 3 7
Sample Output
80
Sol
是个dp题。思路很好想,但是也很难想到——因为连通块的轮廓为凸,所以一定满足:对于连通块里的每一行而言,会有一个左边界和右边界越来越大的过程和逐步缩小的过程。所以易得:
若现在的状态是左右扩大,那么上一行的状态便同样为左右扩大或者没选;
若现在的状态是左扩大右缩小,则上一行应为左扩大右缩小或者左扩大右扩大【表示转折】
若现在的状态是左缩小右扩大,则上一行应为左扩大右扩大或者左缩小右扩大【表示转折】
若现在的状态是左右缩小,那么上一行的状态这里的四种均可。【除了不选】
过度过来的状态决定了枚举的上一行状态的区间左右边界。所以我们的dp里需要存的变量有:,表示第i行,至此共选了j个,这一行选择的是第l~r个,x和y表示状态【纵坐标0增1减,所以左右扩大为[1, 0]】。接下来的递推就很简单了:
例如:.左右扩大
*若j == r - l + 1,则,即从这一行才开始选。
同理可得其他的三个表达式了。【懒得打公式QwQ】同时也为了方便处理,我们的a数组都存为前缀和,这样只要作差就可得到l~r的值。
最后的目标状态为:。因为我们求解过程中的任意状态都是属于凸形,并且如果在某行中间就得到了K个方格,是不会地推到第n行的,所以还要枚举上来找。
先上代码了——【有点恶心,其实核心部分都是可以边写边复制粘贴的QwQ】
#include<bits/stdc++.h>
using namespace std;
int read()
{
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n, m, K;
int w[20][20], dp[20][230][20][20][2][2];
int main()
{
n = read(), m = read(), K = read();
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) w[i][j] = w[i][j - 1] + read();
for(register int i = 1; i <= n; i++)
for(register int j = 0; j <= min(i * m, K); j++)
for(register int l = 1; l <= m; l++)
for(register int r = l; r <= m; r++)//0增1减
{
register int len = r - l + 1;
if(len > j) continue;//这一句不合法判定很重要!!!
//左右扩大,1,0
if(len != j) for(register int p = l; p <= r; p++)//这里特判
for(register int q = p; q <= r; q++)
dp[i][j][l][r][1][0] = max(dp[i][j][l][r][1][0], dp[i - 1][j - len][p][q][1][0]);
else dp[i][j][l][r][1][0] = dp[i - 1][0][0][0][1][0];
//左扩大右收缩, 1,1
for(register int p = l; p <= r; p++)
for(register int q = r; q <= m; q++)
dp[i][j][l][r][1][1] = max(dp[i][j][l][r][1][1], max(dp[i - 1][j - len][p][q][1][1], dp[i - 1][j - len][p][q][1][0]));
//左收缩右扩大,0,0
for(register int p = 1; p <= l; p++)
for(register int q = l; q <= r; q++)
dp[i][j][l][r][0][0] = max(dp[i][j][l][r][0][0], max(dp[i - 1][j - len][p][q][0][0], dp[i - 1][j - len][p][q][1][0]));
//左右收缩,0,1
for(register int p = 1; p <= l; p++)
for(register int q = r; q <= m; q++)
dp[i][j][l][r][0][1] = max(max(dp[i][j][l][r][0][1], max(dp[i - 1][j - len][p][q][0][0], dp[i - 1][j - len][p][q][0][1])), max(dp[i - 1][j - len][p][q][1][0], dp[i - 1][j - len][p][q][1][1]));
//上面的一大坨其实可以写个for循环0~1,看起来会简洁很多,后面关于方案输出的代码就是用for简化的。
dp[i][j][l][r][0][0] += w[i][r] - w[i][l - 1];//最后再来加上
dp[i][j][l][r][0][1] += w[i][r] - w[i][l - 1];
dp[i][j][l][r][1][0] += w[i][r] - w[i][l - 1];
dp[i][j][l][r][1][1] += w[i][r] - w[i][l - 1];
}
int ans = 0;
for(int i = 1; i <= n; i++)
for(int l = 1; l <= m; l++)
for(int r = l; r <= m; r++)
ans = max(max(ans, max(dp[i][K][l][r][0][0], dp[i][K][l][r][0][1])), max(dp[i][K][l][r][1][0], dp[i][K][l][r][1][1]));
//枚举答案【其实也可以简化一下枚举状态的过程QAQ
printf("%d\n", ans);
return 0;
}
/*
5 5 12
7 1 0 1 5
6 8 0 4 10
6 6 9 4 6
9 2 9 1 0
8 0 0 3 7
*/
再来就是输出方案的问题了——一般这种问题其实存一下前导状态然后一路递归回去输出就行了。只是说存的时候会有点麻烦,因为量有点多……
希望有大佬能解答一下如何在64MB以内完成存路径,输出路径的操作QwQ下面的代码是128MB可以过的:
#include<bits/stdc++.h>
using namespace std;
int read()
{
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n, m, K;
long long w[20][20], dp[20][230][20][20][2][2];
int fa[20][230][20][20][2][2][5];//0 = j, 1 = l, 2 = r, 3 = op1, 4 = op2
void dfs(int i, int j, int l, int r, int op1, int op2)
{
if(!i || !j) return;
dfs(i - 1, fa[i][j][l][r][op1][op2][0], fa[i][j][l][r][op1][op2][1], fa[i][j][l][r][op1][op2][2], fa[i][j][l][r][op1][op2][3], fa[i][j][l][r][op1][op2][4]);
for(int x = l; x <= r; x++) printf("%d %d\n", i, x);
}
int main()
{
n = read(), m = read(), K = read();
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) w[i][j] = w[i][j - 1] + read();
for(register int i = 1; i <= n; i++)
for(register int j = 0; j <= min(i * m, K); j++)
for(register int l = 1; l <= m; l++)
for(register int r = l; r <= m; r++)//0增1减
{
register int len = r - l + 1;
if(len > j) continue;
//左右扩大,1,0
if(len != j) {//这里一定要打大括号……
for(register int p = l; p <= r; p++)
for(register int q = p; q <= r; q++)
if(dp[i - 1][j - len][p][q][1][0] > dp[i][j][l][r][1][0])
dp[i][j][l][r][1][0] = dp[i - 1][j - len][p][q][1][0], fa[i][j][l][r][1][0][0] = j - len, fa[i][j][l][r][1][0][1] = p, fa[i][j][l][r][1][0][2] = q, fa[i][j][l][r][1][0][3] = 1, fa[i][j][l][r][1][0][4] = 0;
}//好长的说……后面因为涉及到是哪一个状态前导过来的所以只能枚举状态了。
else dp[i][j][l][r][1][0] = dp[i - 1][0][0][0][1][0], fa[i][j][l][r][1][0][0] = 0, fa[i][j][l][r][1][0][1] = 0, fa[i][j][l][r][1][0][2] = 0, fa[i][j][l][r][1][0][4] = 0, fa[i][j][l][r][1][0][3] = 1;
//左扩大右收缩, 1,1
for(register int p = l; p <= r; p++)
for(register int q = r; q <= m; q++)
for(register int y = 0; y <= 1; y++)
if(dp[i - 1][j - len][p][q][1][y] > dp[i][j][l][r][1][1])
dp[i][j][l][r][1][1] = dp[i - 1][j - len][p][q][1][y], fa[i][j][l][r][1][1][0] = j - len, fa[i][j][l][r][1][1][1] = p, fa[i][j][l][r][1][1][2] = q, fa[i][j][l][r][1][1][3] = 1, fa[i][j][l][r][1][1][4] = y;
//左收缩右扩大,0,0
for(register int p = 1; p <= l; p++)
for(register int q = l; q <= r; q++)
for(register int x = 0; x <= 1; x++)
if(dp[i - 1][j - len][p][q][x][0] > dp[i][j][l][r][0][0])
dp[i][j][l][r][0][0] = dp[i - 1][j - len][p][q][x][0], fa[i][j][l][r][0][0][0] = j - len, fa[i][j][l][r][0][0][1] = p, fa[i][j][l][r][0][0][2] = q, fa[i][j][l][r][0][0][3] = x, fa[i][j][l][r][0][0][4] = 0;
//左右收缩,0,1
for(register int p = 1; p <= l; p++)
for(register int q = r; q <= m; q++)
for(register int x = 0; x <= 1; x++)
for(register int y = 0; y <= 1; y++)
if(dp[i - 1][j - len][p][q][x][y] > dp[i][j][l][r][0][1])
dp[i][j][l][r][0][1] = dp[i - 1][j - len][p][q][x][y], fa[i][j][l][r][0][1][0] = j - len, fa[i][j][l][r][0][1][1] = p, fa[i][j][l][r][0][1][2] = q, fa[i][j][l][r][0][1][3] = x, fa[i][j][l][r][0][1][4] = y;
dp[i][j][l][r][0][0] += w[i][r] - w[i][l - 1];
dp[i][j][l][r][0][1] += w[i][r] - w[i][l - 1];
dp[i][j][l][r][1][0] += w[i][r] - w[i][l - 1];
dp[i][j][l][r][1][1] += w[i][r] - w[i][l - 1];
}
register long long ans = 0;
register int is, ls, rs, op1, op2;
for(register int i = 1; i <= n; i++)
for(register int l = 1; l <= m; l++)
for(register int r = l; r <= m; r++)
for(register int x = 0; x <= 1; x++)//这里也必须手动枚举状态
for(register int y = 0; y <= 1; y++)
if(dp[i][K][l][r][x][y] > ans)
ans = dp[i][K][l][r][x][y], is = i, ls = l, rs = r, op1 = x, op2 = y;
printf("Oil : %lld\n", ans);
dfs(is, K, ls, rs, op1, op2);
return 0;
}
迎评:)
——End——