【线性dp】 [SCOI2009]粉刷匠
点个关注点个赞吧
-
一道比较简单的线性dp题目
前置知识:会手推一些简单的状态转移方程、较为熟练地掌握背包问题模型
[SCOI2009]粉刷匠
题目描述
windy有
windy每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色。 每个格子最多只能被粉刷一次。
如果windy只能粉刷
一个格子如果未被粉刷或者被粉刷错颜色,就算错误粉刷。
输入格式
第一行包含三个整数,
接下来有
输出格式
包含一个整数,最多能正确粉刷的格子数。
样例 #1
样例输入 #1
3 6 3
111111
000000
001100
样例输出 #1
16
提示
解题过程
-
条木板,只能刷 次,求最多能正确粉刷多少格子,而且每个格子只能被刷一次;
刷错和不刷都算没有正确粉刷
-
似乎有点难以思考?
好像每刷一次,既不知道刷的起点在哪儿,也不知道刷到哪个位置停下,然后换另一种颜色;
而且,中间好像还可以空一些格子不刷颜色;
那我们先放一放 😃
-
有一点是很显然的:木板的长度是
,所以显然一条木板最少刷 次(全部一次性刷完,刷一种颜色),最多刷 次(每个格子都用对应的颜色去刷,每次刷的连续区间长度为1);
:假设现在我告诉你,每一条木板刷 次能得到正确粉刷的格子的最大值,你会做这道题吗?
:这个简单!不就是已知每个木板刷 次的价值,总共刷 次,求最大价值!
- 典型的背包啊,还是个分组的,
是背包总容量, 是每个物品的体积,一条木板从 ~ 的粉刷次数对应的价值就是一个组,只能取其中的一个(你总不可能搞出两个同一条木板来粉刷吧),也就是常见的背包模型:共有 个组,每个组里有 个物品,每个物品都有一个体积( ~ )和价值( ~ ),背包容量为 ,求最大价值
-
完美的解决了一半问题
可是我们要怎么知道每一条木板刷
次,能得到的正确粉刷格子的最大值呢?这个问题呢,确实是一个问题
方法一(经典作法)
-
对于每一条木板,我们思考,如何计算出粉刷
次后每条木板能得到的最大正确粉刷格子数 -
状态设的好,转移随便搞。 ————沃·兹基硕德
:你觉得怎么设状态比较合适?
:我觉得吧,假设从左往右刷,对于接下来的刷格子的行为,之前刷了多少个格子,以及刷了多少次比较重要,可以考虑设状态时把这几个量加进去
-
所以我们可以设
表示对于第 块木板,这一次刷刷到了第 个格子,总共刷了 次;怎么转移呢?
我们可以枚举上一次刷格子刷到了哪个位置(假设是
),然后刷的次数肯定是 次,则转移很容易弄出:其中
表示这块木板一次性从 刷到 能最多正确刷对的格子数(这一段无非就是全刷 或者全刷 ,比较一下哪种更好, 数组预处理一下就好) -
那这个转移就搞定了 😃 ,是不是很容易呢,代码就不贴了
,懒得写最后的最后把
数组当成物品,去做背包,这个题就搞定了,耶!
:Wait!那如果空着格子不刷呢?
:宝,刷错和不刷是等价的啦,与去说空着一些格子不刷,我还不如就刷错呢,更方便计算与转移呀。其实这是一个小小的贪心啦!
方法二(Truman_2022瞎整的,好像没看到用这个方法的人)
-
可以思考一下每次就只刷
格,如何做呢? -
那如果是这样,似乎上一次刷格子,刷的是
还是 也很重要
-
那么我们可以这样设状态:(比方法一要差,因为
数组空间是法一的两倍,但在可接受范围内,而且不用预处理 数组,各有优劣吧) 表示第 块木板,刷了 次,刷了 个格子,第 个格子刷的是 还是 所能的到的最大涂正确的格子数
那么对于现在涂到了第
-
如果第
个格子原本应该涂成 ,那么我有给它涂成 和涂成 两种方案:如果我给它涂成
,正确涂格子数肯定要 ,但是上一次怎么涂的呢?我们又要分类讨论:1.上一次如果涂的也是
,我们可以选择将这次涂格子的过程合并到上次,即仍然总共只涂了 次
dp[i][j][k][0]=max(dp[i][j][k][0],dp[i][j-1][k][0]+1);
2.假设上一次仍然是涂的
dp[i][j][k][0]=max(dp[i][j][k][0],dp[i][j-1][k-1][0]+1);
3.那如果上次涂的是
dp[i][j][k][0]=max(dp[i][j][k][0],dp[i][j-1][k-1][1]+1);
-
如果第
个格子原本应该涂成 ,那么情况就和刚才的几乎一样啦,就是把 和 反过来了: -
最后的最后的最后,对于法二,贴个代码呗
#include <bits/stdc++.h>
using namespace std;
#define N (50+1)
#define T (2500+1)
#define endl "\n"
int n,m,t;
string wood[N];
int dp1[N][N][N][2];//dp1,第i(以下代码里面为u)块木板,刷了k次,刷了j(以下代码里面为i)个格子,第j(以下代码里面为i)个格子刷的是0还是1所能的到的最大涂正确的格子数
int dp2[N][T];//dp2,背包
void solve1_linerdp(int u){//solve1,线性dp,算dp1
dp1[u][0][0][0]=dp1[u][0][0][1]=0;
for(int i=1;i<=m;i++){
for(int k=1;k<=i;k++){
if(wood[u][i]=='0'){
dp1[u][i][k][0]=max(dp1[u][i][k][0],dp1[u][i-1][k][0]+1);
dp1[u][i][k][0]=max(dp1[u][i][k][0],dp1[u][i-1][k-1][0]+1);
dp1[u][i][k][0]=max(dp1[u][i][k][0],dp1[u][i-1][k-1][1]+1);
dp1[u][i][k][1]=max(dp1[u][i][k][1],dp1[u][i-1][k][1]);
dp1[u][i][k][1]=max(dp1[u][i][k][1],dp1[u][i-1][k-1][0]);
}
else{
dp1[u][i][k][1]=max(dp1[u][i][k][1],dp1[u][i-1][k-1][1]+1);
dp1[u][i][k][1]=max(dp1[u][i][k][1],dp1[u][i-1][k][1]+1);
dp1[u][i][k][1]=max(dp1[u][i][k][1],dp1[u][i-1][k-1][0]+1);
dp1[u][i][k][0]=max(dp1[u][i][k][0],dp1[u][i-1][k][0]);
dp1[u][i][k][0]=max(dp1[u][i][k][0],dp1[u][i-1][k-1][1]);
}
}
}
}
void solve2_backpack(){//solve2,算背包
for(int i=1;i<=n;i++){
for(int w=1;w<=m;w++){
int c=max(dp1[i][m][w][0],dp1[i][m][w][1]);
for(int j=t;j>=0;j--){
if(j-w>=0) dp2[i][j]=max(dp2[i][j],dp2[i-1][j-w]+c);
}
}
}
int ans=0;
for(int i=1;i<=t;i++){
ans=max(ans,dp2[n][i]);
}
cout<<ans<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m>>t;
for(int i=1;i<=n;i++) cin>>wood[i],wood[i]=" "+wood[i];
memset(dp1,-0x3f,sizeof(dp1));
for(int i=1;i<=n;i++){
solve1_linerdp(i);
}
solve2_backpack();
return 0;
}
全剧终
尾声:md写题解也太浪费时间了吧,以后有空(比如放长假了)再玩玩儿吧,bye~
屑题解
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App