[洛谷P4158][SCOI2009]粉刷匠(动态规划)

题目描述

windy有 N 条木板需要被粉刷。 每条木板被分为 M 个格子。 每个格子要被刷成红色或蓝色。
windy每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色。 每个格子最多只能被粉刷一次。
如果windy只能粉刷 T 次,他最多能正确粉刷多少格子?
一个格子如果未被粉刷或者被粉刷错颜色,就算错误粉刷。

输入描述

第一行包含三个整数,N M T。
接下来有N行,每行一个长度为M的字符串,'0’表示红色,'1’表示蓝色。
30%的数据,满足 1 <= N,M <= 10 ; 0 <= T <= 100 。
100%的数据,满足 1 <= N,M <= 50 ; 0 <= T <= 2500 。

输出描述

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

示例

输入

3 6 3
111111
000000
001100

输出

16

题目思路

首先一块木板要刷的话一定会全部刷完,因为不刷或者刷错了都不算数,所以干脆刷完,即使刷错了也不要紧,有可能还能多刷对几块。例如001100,刷一次,全刷红色,就能有4块对的。

再分析一块木板刷k次最多能正确粉刷多少格子呢?
我们可以先把这块木板连续续相同颜色格子合并起来,例如001100这块板子看作2 2 2,代表有三块连续的相同颜色块数,且相邻两块颜色不同。很容易想到这块木板的粉刷一定是刷红刷蓝交替进行的,且后一次粉刷可以利用前一次粉刷的结果,所以可以用dp解决一块木板最多能正确粉刷的格子数。

用f[i][j][l]表示这块木板从左到右刷了i次,刷完了第j块,且最后一次粉刷的颜色是l,那么很容易想到转移方程:
f[i][j][l] = max(f[i][j][l],f[i-1][k][l^1]+第k+1块到第j块颜色为l的格子数)(0<=k<j)
注意第k+1块到第j块颜色为l的格子数可以提前预处理用一个数组记录下来
然后把这块木板刷k次最多能正确粉刷的格子数用一个vector保存下来

知道了一块木板刷k次最多能正确粉刷多少格子后就很容易能想到i块木板刷j次的最多正确粉刷格子数的dp解法
dp[i][j]表示前i块木板刷了j次的最多正确粉刷格子数,那么可以很容易得到状态转移方程:
dp[i][j] = max(dp[i-1][j-k]+第i块木板刷k次最多能正确粉刷的格子数)(0<=k<=木板的连续块数)
最后统计一下答案就出来了

代码

#include<bits/stdc++.h>
using namespace std;
int n,m,t,dp[55][2505],ans;
string s[55];
vector<int>v[55];//保存每一块木板粉刷k次能正确多少 
void preProcess(int x)//预处理每一块木板粉刷k次能正确多少 
{
	vector<int>y,z;
	int now = s[x][0]-'0',sum = 0,f[55][55][2] = {0},p[55][55][2] = {0},maxn = 0;
	y.push_back(0);//因为vector下标从0开始,所以加一个0代表刷0次正确0块,
	z.push_back(0);//z代表每一块的颜色是什么,方便后面统计第i到j块颜色为l的格子数
	//将木板连续相同颜色格子合并 
	for(int i = 0;i<m;i++)
	{
		if(now==0&&s[x][i]=='0'||now==1&&s[x][i]=='1')
			sum++;
		if(now==0&&s[x][i]=='1'&&sum!=0)
			y.push_back(sum),z.push_back(0),now = 1,sum = 1;
		if(now==1&&s[x][i]=='0'&&sum!=0)
			y.push_back(sum),z.push_back(1),now = 0,sum = 1;
	}
	if(sum!=0)
		y.push_back(sum),z.push_back(s[x][m-1]-'0');
	sum = y.size();
	//预处理第i到j块颜色为l的格子数 
	for(int i = 0;i<sum;i++)
		for(int j = i;j<sum;j++)
			for(int k = i;k<=j;k++)
				for(int l = 0;l<=1;l++)
					p[i][j][l]+=y[k]*(z[k]==l);
	//得到这块木板从左到右刷i次刷完第j块且最后一次粉刷颜色为l最多能有多少格子正确 
	for(int i = 1;i<sum;i++)
		for(int j = 1;j<sum;j++)
			for(int k = 0;k<j;k++)
				for(int l = 0;l<=1;l++)
					f[i][j][l] = max(f[i][j][l],f[i-1][k][l^1]+p[k+1][j][l]);
	//统计保存这块木板结果到vector中 
	for(int i = 0;i<sum;i++,v[x].push_back(maxn),maxn = 0)
		for(int j = 0;j<sum;j++)
			maxn = max(maxn,max(f[i][j][0],f[i][j][1]));
}
int main()
{
	cin>>n>>m>>t;
	for(int i = 1;i<=n;i++)
		cin>>s[i],preProcess(i);
	//得到刷完第i块木板共刷了j次且最多能有多少格子正确 
	for(int i = 1;i<=n;i++)
		for(int j = 1;j<=t;j++)
			for(int k = 0;k<v[i].size()&&k<=j;k++)
				dp[i][j] = max(dp[i][j],dp[i-1][j-k]+v[i][k]);
	for(int i = 1;i<=n;i++)
		ans = max(ans,dp[i][t]);
	cout<<ans;	
	return 0;
} 

欢迎关注微信公众号:Java后台开发

致力于分享原创计算机与软件开发知识及SSM、Spring cloud、Redis、微服务等Java后端开发技术
公众号里还有很多开发工具及学习资料

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值