admin 管理员组文章数量: 1184232
【题解】P4158 [SCOI2009]粉刷匠(DP,背包)
【题解】P4158 [SCOI2009]粉刷匠
是一道资源规划 DP 的好题,但是我想了很久还去看了题解。/kk我真菜。
题目链接
P4158 [SCOI2009]粉刷匠 - 洛谷
题意概述
发现自己实在不会简化题意于是就抄了原题(雾)。
windy 有 \(N\) 条木板需要被粉刷。 每条木板被分为 \(M\) 个格子。 每个格子要被刷成红色或蓝色。
windy 每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色。 每个格子最多只能被粉刷一次。
如果 windy 只能粉刷 \(T\) 次,他最多能正确粉刷多少格子?
一个格子如果未被粉刷或者被粉刷错颜色,就算错误粉刷。
思路分析
这题我至少读错了有 1h 之久……
题目要求的是粉刷 \(T\) 次最多的正确格子数。
比较直接的思路是,定义 \(dp_{i,j}\) 表示前 \(i\) 行木板,粉刷 \(j\) 次最多的正确格子数。
那么对于第 \(i\) 行状态可以从第 \(i-1\) 行转移而来,显然这是一个 01 背包。
那么有:
\[dp_{i,j}=\max \limits_{1 \le k \le j}(dp_{i-1,k}+cnt_{i,k}) \]其中 \(cnt_{i,k}\) 表示的是第 \(i\) 行粉刷 \(k\) 次最多的正确格子数。
显然这个 \(cnt\) 可以对每一行分别求。那么我们也对每一行分别考虑。
对于第 \(i\) 行:
设 \(g_{j,k}\) 表示前 \(j\) 个位置粉刷 \(j\) 次最多的正确格子数。
考虑如何转移。
比较显然的是,对于前 \(j\) 个位置,肯定是由:连续一段 1,连续一段 0,连续一段 1……这样 01 的连续段相互交错形成的,那么我们可以将这些连续的段切成几部分得到 \(g_{j,k}\) 的转移。
可以考虑利用类似区间 DP 的思想,枚举断点 \(l\),将前 \(j\) 个位置划分为前 \(l\) 个位置和区间 \([l+1,j]\) 两段,其中 \([l+1,j]\) 作为最后一次涂色的区间。
那么 \(g_{j,k}\) 就可以由 \(g_{l,k-1}\) 转移得到,即:
\[g_{j,k}=\max \limits_{0 \le l < j}(g_{l,k-1}+mx_{l+1,j}) \](注意 \(l\) 的范围)
其中 \(mx_{l,r}\) 表示的是区间 \([l,r]\) 中数量最多的颜色的数量。
\(mx_{l,r}\) 可以直接暴力枚举得到。
那么最后的答案就是 \(dp_{n,t}\)。
梳理一下求解过程:
求粉刷 \(T\) 次最多的正确格子数 \(\rightarrow\) 求 \(dp_{i,j}\) \(\rightarrow\) 求 \(g_{j,k}\)(将求整个矩形转化为一行)\(\rightarrow\) 求 \(mx_{l+1,j}\)(将 \(k\) 次转化为 1 次)。
求解步骤
对于每一行 \(i\):
-
暴力枚举求出 \(mx_{l,r}\):\(1-m\) 所有区间。
-
求 \(g_{j,k}\);
-
求 \(dp_{i,j}\)。
易错点
-
每次枚举一个新的 \(i\) 都要将 \(g\) 数组和 \(mx\) 数组清空;
-
在求 \(g_{j,k}\) 时枚举的断点 \(l\) 范围是 \([0,j)\);
-
\(dp_{n,t}\) 第二维由于 \(t\) 的范围是 2500,所以要开 \(>2500\);
一些思考
这道题我刚开始读错了好久的题意,并在读错的题意上思考了好久但是并没有想出来。
但是我总觉得我读错的那个题意也可以作为一道题目,并且一定有正确的解法。(盲目自信)
我刚开始没有看到题目中“每个格子只能被粉刷一次”这句话。
然后感觉可以先考虑将每一行所有的格子涂色正确的情况整出来。也就是,将第 \(i\) 行涂正确至少需要多少次。
那么发现发现对于单个的一行,实际上就是P4170 [CQOI2007]涂色 - 洛谷。
那么可以用 \(dp_{i,l,r}\) 表示第 \(i\) 行区间 \([l,r]\) 涂正确的最少次数,然后做区间 DP。
然后感觉是个有点类似于贪心和 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 什么时候才能有质的突破呢?
本文标签: 题解P4158 SCOI2009粉刷匠(DP,背包)
版权声明:本文标题:【题解】P4158 [SCOI2009]粉刷匠(DP,背包) 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.roclinux.cn/p/1698021990a283031.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论