【题解】P4158 [SCOI2009]粉刷匠(DP,背包)
【题解】P4158 [SCOI2009]粉刷匠
是一道资源规划 DP 的好题,但是我想了很久还去看了题解。/kk我真菜。
题目链接
题意概述
发现自己实在不会简化题意于是就抄了原题(雾)。
windy 有
windy 每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色。 每个格子最多只能被粉刷一次。
如果 windy 只能粉刷
一个格子如果未被粉刷或者被粉刷错颜色,就算错误粉刷。
思路分析
这题我至少读错了有 1h 之久……
题目要求的是粉刷
比较直接的思路是,定义
那么对于第
那么有:
其中
显然这个
对于第
设
考虑如何转移。
比较显然的是,对于前
可以考虑利用类似区间 DP 的思想,枚举断点
那么
(注意
其中
那么最后的答案就是
梳理一下求解过程:
求粉刷
求解步骤
对于每一行
-
暴力枚举求出
: 所有区间。 -
求
; -
求
。
易错点
-
每次枚举一个新的
都要将 数组和 数组清空; -
在求
时枚举的断点 范围是 ; -
第二维由于 的范围是 2500,所以要开 ;
一些思考
这道题我刚开始读错了好久的题意,并在读错的题意上思考了好久但是并没有想出来。
但是我总觉得我读错的那个题意也可以作为一道题目,并且一定有正确的解法。(盲目自信)
我刚开始没有看到题目中“每个格子只能被粉刷一次”这句话。
然后感觉可以先考虑将每一行所有的格子涂色正确的情况整出来。也就是,将第
那么发现发现对于单个的一行,实际上就是P4170 [CQOI2007]涂色 - 洛谷。
那么可以用
然后感觉是个有点类似于贪心和 01 背包的东西,但一直没想出解法。
虽然说我看错题目很傻逼,也没有想到看错了的题意的正确解法(我太菜了),但是我还是把它记录了下来,希望以后可以有所突破。或者,看到这篇文章的你如果有所想法,可以在下方留言或者联系我说出你的解法。
毕竟探索本身,就是一件很有意义的事情嘛。
实现代码
//luoguP4158
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=55;
const int maxt=maxn*maxn;
int a[maxn][maxn],dp[maxn][maxt],g[maxn][maxn],mx[maxn][maxn];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int main()
{
int n,m,t;
n=read();m=read();t=read();
for(int i=1;i<=n;i++)
{
string s;
cin>>s;
for(int j=0;j<m;j++)
{
if(s[j]=='1')a[i][j+1]=1;//bug:j 写成 i……
else a[i][j+1]=0;
}
}
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=m;j++)cout<<a[i][j]<<" ";
// cout<<endl;
// }
for(int i=1;i<=n;i++)
{
memset(g,0,sizeof(g));
memset(mx,0,sizeof(mx));
int cnt1=0,cnt0=0;
for(int l=1;l<=m;l++)
{
if(a[i][l]==0)cnt0=1,cnt1=0;
else cnt1=1,cnt0=0;
mx[l][l]=1;
for(int r=l+1;r<=m;r++)
{
if(a[i][r]==0)cnt0++;
else cnt1++;
mx[l][r]=(cnt0>cnt1)?cnt0:cnt1;
// cout<<cnt0<<" "<<cnt1<<endl;
// cout<<i<<" "<<l<<" "<<r<<" "<<mx[l][r]<<endl;
}
}
for(int j=1;j<=m;j++)
{
for(int k=1;k<=j;k++)
{
for(int l=0;l<=j;l++)//bug: j 的范围:[0,j)
{
// cout<<l<<" "<<" "<<g[l][k-1]<<" "<<mx[l+1][j]<<endl;
g[j][k]=max(g[j][k],g[l][k-1]+mx[l+1][j]);//注意这里没有等于即 l!=k 且 l 需要从 0 开始枚举
}
// cout<<i<<" "<<j<<" "<<k<<" "<<g[j][k]<<endl;
}
}
for(int j=t;j>=1;j--)
{
for(int k=1;k<=j;k++)dp[i][j]=max(dp[i][j],dp[i-1][j-k]+g[m][k]);
// cout<<i<<" "<<j<<" "<<dp[i][j]<<endl;
}
}
cout<<dp[n][t]<<endl;
return 0;
}
/*mx[l][r] 表示的是对于一块木板的区间 [l,r] 这一段最多的颜色的数量。
g[i][j] 表示的是考虑了前 i 个位置,粉刷 j 次的最多正确粉刷的个数。
g[i][j]=max(g[i][j],g[l][j-1]+mx[l+1][i]).
dp[i][j] 表示考虑了前 i 行,粉刷 j 次的最多正确粉刷个数。
显然这是 01 背包。那么有:
dp[i][j]=max(dp[i][j],dp[i-1][j-k]+g[m][k]).
*/
感觉我还是好菜啊。做了那么多 DP 的题目,但是还是对有些题毫无感觉,甚至设计不出 DP 状态,也不知道我的 DP 什么时候才能有质的突破呢?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现