21.2.21 T3 粉刷匠 log P4158 && SCOI 2009 总结

1 篇文章 0 订阅

题面

Description

“我是一个粉刷匠,粉刷本领强~~~”粉刷匠qjx 哼着小曲高兴地开始了一天的工
作,这天qjx 有 n n n 条木板需要被粉刷。每条木板被分成 m m m 个格子,每个格子要被刷
成红色或蓝色。qjx 每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种
颜色,已知每个格子最多只能被粉刷一次。
如果qjx 只能粉刷 t t t 次,她最多能正确粉刷多少格子。
注意,一个格子如果未被粉刷或被粉刷成错误颜色,就算粉刷错误。

Input

第一行三个数n,m,t;
接下来n 行,每行一个长度为m 的字符“0”表示红色,“1”表示蓝色。

Output

一个整数,最多能正确粉刷的格子数。

Sample Input

3 6 3
111111
000000
001100

Sample Output
数据范围与约定

10% 数据满足l≤n, m≤10。
100%数据满足l≤n, m≤50:0≤t≤2500。

题解

这道题把行和列分开看很明显是两个很简单的DP题,但是DP是我学的最烂的地方,所以考场上啥也没写出来。。。
首先把行分开看,设dp[i][j][0]表示前i列,涂了j次,且当前,也就是第i列涂的是红色是正确的个数,dp[i][j][1]则表示当前涂的是蓝色时正确的个数。
把数组设好后,转移方程自然就很好想。首先是把行分隔开来看的dp,当枚举到某行第i列时,木板有两种情况,红和蓝,而我们的dp也有两种情况,涂与不涂,如果涂了之后,涂的颜色是正确的,那么转移之后正确的个数自然要 + 1。由此可以写出状态转移方程。

if(a[k][i] == 1) {
				dp[i][j][0] = max(dp[i - 1][j][0],dp[i - 1][j - 1][1]);
				dp[i][j][1] = max(dp[i - 1][j][1],dp[i - 1][j - 1][0]) + 1; 
			}
			else {
				dp[i][j][0] = max(dp[i - 1][j][0],dp[i - 1][j - 1][1]) + 1;
				dp[i][j][1] = max(dp[i - 1][j][1],dp[i - 1][j - 1][0]);
			}

通过这个,我们可以把每行刷j次的正确个数求出来,那这个东西有什么作用呢?开头说过,这道题可以把行与列分开看。那如果我们把每行看成是一个物品,那这道题是否就转换成了一个背包问题了呢?我们用另一个数组g[k][j]存下第k行刷j次的正确个数。那这个数组是否就可以看作是一个体积为j,价值为g[k][j]的物品了呢。那接下来的第二次dp就很好想了。我们用f[j]表示刷j次的正确个数(这个定义是建立在把每行看做是一个物品的基础上的),那么最后答案在f[j]中取最大值即可。
两次dp都要注意一个细节,因为每一格只能刷一次,所以当列数小于 t t t 的时候我们每一行最多只能刷 m m m 次。

完整代码如下

#include<cstdio>
#include<cstring>
const int N = 55;
const int T = 2505;
int dp[N][T][2],f[T],g[N][T],a[N][N],n,m,t,mzy,ans = 0;
inline int max(int a,int b) {
	if(a > b) return a;
	return b;
}
inline int min(int a,int b) {
	if(a < b) return a;
	return b;
}
void DP(int k) {
	memset(dp,0,sizeof(dp));
	for(int i = 1; i <= m; i++)
		for(int j = 1; j <= mzy; j++) {
			if(a[k][i] == 1) {
				dp[i][j][0] = max(dp[i - 1][j][0],dp[i - 1][j - 1][1]);
				dp[i][j][1] = max(dp[i - 1][j][1],dp[i - 1][j - 1][0]) + 1; 
			}
			else {
				dp[i][j][0] = max(dp[i - 1][j][0],dp[i - 1][j - 1][1]) + 1;
				dp[i][j][1] = max(dp[i - 1][j][1],dp[i - 1][j - 1][0]);
			}
		}
	for(int j = 1; j <= mzy; j++) g[k][j] = max(dp[m][j][0],dp[m][j][1]);
}
int main() {
	scanf("%d%d%d",&n,&m,&t);
	mzy = min(m,t);
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= m; j++)
		scanf("%1d",&a[i][j]);
		DP(i);
	}
	for(int i = 1; i <= n; i++)
		for(int j = t; j >= 0; j--) {
			int yzm = min(j,mzy);
			for(int k = 0; k <= yzm; k++)
			f[j] = max(f[j],f[j - k] + g[i][k]);
		}
	for(int i = 0; i <= t; i++) ans = max(ans,f[i]);
	printf("%d",ans);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值