bzoj·入门OJ·Sgu167 I-country

初见安~本题解参考: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里需要存的变量有:dp[i][j][l][r][x][y],表示第i行,至此共选了j个,这一行选择的是第l~r个,x和y表示状态【纵坐标0增1减,所以左右扩大为[1, 0]】。接下来的递推就很简单了:

例如:.左右扩大

dp[i][j][l][r][1][0] = \sum _{p = l}^{r} {a[p]} + max_{1 <=p <= l <= r <= q <= m} \begin{Bmatrix} dp[i - 1][j - (r - l + 1)][p][q][1][0] \end{Bmatrix}

*若j == r - l + 1,则dp[i][j][l][r][1][0] = dp[i - 1][0][0][0][1][0],即从这一行才开始选。

同理可得其他的三个表达式了。【懒得打公式QwQ】同时也为了方便处理,我们的a数组都存为前缀和,这样只要作差就可得到l~r的值。

最后的目标状态为:dp[i][K][l][r][x][y]。因为我们求解过程中的任意状态都是属于凸形,并且如果在某行中间就得到了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——

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值