【dp】SCOI2009粉刷匠 纪中集训提高B组

题目传送门

jzoj1035

Description

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

Input

第一行包含三个整数,N M T。
接下来有N行,每行一个长度为M的字符串,'0’表示红色,'1’表示蓝色。

Output

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

Sample Input

3 6 3
111111
000000
001100

Sample Output

16

Hint

100%的数据,满足 1 <= N,M <= 50 ; 0 <= T <= 2500 。


考场上第一眼以为是贪心。
后面发现不对,觉得是dp,但是它分了很多块木块就不知道怎么处理。(如果是一块整的就大概知道怎么做)
考场上只做了特殊情况,只有10分。

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define N 55
#define ll long long
#define INF 0x3f3f3f3f
int n,m,t;
struct node{
	int r[N],b[N],t1,t2;
}a[N];
int tmp[N];
bool cmp(int p,int q)
{
	return p>q;
}
int main()
{
	int res=0,seg=0;
	scanf("%d %d %d",&n,&m,&t);
	for(int i=1;i<=n;i++)
	{
		char s[N];
		scanf("%s",s+1);
		int cnt=1,j=2;
		a[i].t1=0,a[i].t2=0;
		while(j<=m)
		{
			while(s[j]==s[j-1]&&j<=m)
				j++,cnt++;
			if(s[j-1]=='0') a[i].r[++a[i].r[0]]=cnt,a[i].t1+=cnt;
			else a[i].b[++a[i].b[0]]=cnt,a[i].t2+=cnt;
			res+=cnt,seg++;
			cnt=1,j++;
		}
	}
	if(t<=n)
	{
		for(int i=1;i<=n;i++)
			tmp[i]=max(a[i].t1,a[i].t2);
		sort(tmp+1,tmp+n+1,cmp);
		int ans=0;
		for(int i=1;i<=t;i++)
			ans+=tmp[i];
		printf("%d\n",ans);
		return 0;
	}
	if(t>=seg)
	{
		printf("%d\n",res);
		return 0;
	}
	
	return 0;
}

当初搞完特殊情况之后就在想一般情况,然后就在想怎么给每个木板分配粉刷次数T。如果把T拆成n个正整数的话,感觉这本身就是一个复杂度很高的爆搜2333。然后就不知道怎么处理多块木板了。

结果正解是多次dp,对嘛,不知道怎么处理多块木板的话,就把每块木板当成一个整体再dp一次。

评讲说是分组背包,把每块木板当成一个整体再dp一次那里确实是分组背包,因为每块木板涂的次数是相互冲突的。(不能既涂1次,又涂2次)

感觉这种思想很奇妙,就像是眼光逐渐从局部到整体,先dp每块木板在涂不同次数下的最优值,再把这些木板当成整体,来分配总的T次粉刷。

考场上还贪心地想到最好是相同颜色的肯定是要只用一次刷子(如上程序),但是dp的时候这么写的话细节处理很繁琐,容易写错,所以把每个格子当成一个个体就好。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define N 55
#define M 2005
int f[N][M],g[N][M][N],sum[N][M];
int n,m,t;
/*
f[i][j]:前i行涂j次的最大正确格子数 
g[i][j][k]:第i行涂j次 涂到前k个格子的最大数量
//(注意是第i行 真正约束状态的是j&k 有个i只是因为它有很多块木板 f[][]才是连接各个木板关系的)
sum[i][j]:第i行前j个格子中1的数量 
*/
int main()
{
    scanf("%d %d %d",&n,&m,&t);
    for(int i=1;i<=n;i++)
    {
    	char s[105];
    	scanf("%s",s+1);
    	sum[i][0]=0;
    	for(int j=1;j<=m;j++)
    		sum[i][j]=sum[i][j-1]+s[j]-'0';
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int k=j;k<=m;k++)//涂j次 至少都会涂到j个
				for(int q=j-1;q<=k-1;q++)//枚举涂j-1次时涂到的是哪里 
					g[i][j][k]=max(g[i][j][k],g[i][j-1][q]+max(sum[i][k]-sum[i][q],k-q/*总格子数*/-(sum[i][k]-sum[i][q])));
	for(int i=1;i<=n;i++)
		for(int j=1;j<=t;j++)
			for(int k=0;k<=min(j,m);k++)//在当前这一行涂k次
				f[i][j]=max(f[i][j],f[i-1][j-k]+g[i][k][m]); 
	printf("%d\n",f[n][t]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值