【线性dp】 [SCOI2009]粉刷匠

点个关注点个赞吧


  • 一道比较简单的线性dp题目


前置知识:会手推一些简单的状态转移方程、较为熟练地掌握背包问题模型


[SCOI2009]粉刷匠

题目描述

windy有 \(N\) 条木板需要被粉刷。 每条木板被分为 \(M\) 个格子。 每个格子要被刷成红色或蓝色

windy每次粉刷,只能选择一条木板上一段连续的格子,然后涂上一种颜色。 每个格子最多只能被粉刷一次。

如果windy只能粉刷 \(T\),他最多能正确粉刷多少格子?

一个格子如果未被粉刷或者被粉刷错颜色,就算错误粉刷。

输入格式

第一行包含三个整数,\(N\) \(M\) \(T\)

接下来有\(N\)行,每行一个长度为\(M\)的字符串,'\(0\)'表示红色,'\(1\)'表示蓝色。

输出格式

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

样例 #1

样例输入 #1

3 6 3
111111
000000
001100

样例输出 #1

16

提示

\(30\)%的数据,满足 \(1 <= N,M <= 10 ; 0 <= T <= 100\)

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



解题过程


  • \(N\) 条木板,只能刷\(T\)次,求最多能正确粉刷多少格子,而且每个格子只能被刷一次;
    刷错和不刷都算没有正确粉刷

  • 似乎有点难以思考?

    好像每刷一次,既不知道刷的起点在哪儿,也不知道刷到哪个位置停下,然后换另一种颜色;

    而且,中间好像还可以空一些格子不刷颜色;

    那我们先放一放 😃

  • 有一点是很显然的:木板的长度是\(M\),所以显然一条木板最少刷\(1\)次(全部一次性刷完,刷一种颜色),最多刷\(M\)次(每个格子都用对应的颜色去刷,每次刷的连续区间长度为1);

\(I\):假设现在我告诉你,每一条木板刷\(k\)次能得到正确粉刷的格子的最大值,你会做这道题吗?


\(Y\):这个简单!不就是已知每个木板刷\(k\)次的价值,总共刷\(T\)次,求最大价值!

  • 典型的背包啊,还是个分组的,\(T\)是背包总容量,\(k\)是每个物品的体积,一条木板从\(1\) ~ \(m\)的粉刷次数对应的价值就是一个组,只能取其中的一个(你总不可能搞出两个同一条木板来粉刷吧),也就是常见的背包模型:共有\(N\)个组,每个组里有\(M\)个物品,每个物品都有一个体积(\(1\) ~ \(m\))和价值(\(1\) ~ \(m\)),背包容量为\(T\),求最大价值



  • 完美的解决了一半问题

    可是我们要怎么知道每一条木板刷\(k\)次,能得到的正确粉刷格子的最大值呢?

    这个问题呢,确实是一个问题

方法一(经典作法)

  • 对于每一条木板,我们思考,如何计算出粉刷\(k\)次后每条木板能得到的最大正确粉刷格子数

  • 状态设的好,转移随便搞。 ————沃·兹基硕德

\(I\):你觉得怎么设状态比较合适?


\(Y\):我觉得吧,假设从左往右刷,对于接下来的刷格子的行为,之前刷了多少个格子,以及刷了多少次比较重要,可以考虑设状态时把这几个量加进去

  • 所以我们可以设\(dp[i][j][k]\)表示对于第\(i\)块木板,这一次刷刷到了第\(j\)个格子,总共刷了\(k\)

    怎么转移呢?

    我们可以枚举上一次刷格子刷到了哪个位置(假设是\(l\)),然后刷的次数肯定是\(k-1\)次,则转移很容易弄出:\(dp[i][j][k]=max{dp[i][l][k-1]+w[l+1][j]}\)

    其中\(w[l][r]\)表示这块木板一次性从\(l\)刷到\(r\)能最多正确刷对的格子数(这一段无非就是全刷\(1\)或者全刷\(0\),比较一下哪种更好,\(w\)数组预处理一下就好)

  • 那这个转移就搞定了 😃 ,是不是很容易呢,代码就不贴了,懒得写

    最后的最后把\(dp\)数组当成物品,去做背包,这个题就搞定了,耶!

\(Y\):Wait!那如果空着格子不刷呢?


\(I\):宝,刷错和不刷是等价的啦,与去说空着一些格子不刷,我还不如就刷错呢,更方便计算与转移呀。其实这是一个小小的贪心啦!


方法二(Truman_2022瞎整的,好像没看到用这个方法的人)

  • 可以思考一下每次就只刷\(1\)格,如何做呢?

  • 那如果是这样,似乎上一次刷格子,刷的是\(0\)还是\(1\)也很重要


  • 那么我们可以这样设状态:(比方法一要差,因为\(dp\)数组空间是法一的两倍,但在可接受范围内,而且不用预处理\(w\)数组,各有优劣吧)

    \(dp[i][j][k][0/1]\)表示\(i\)块木板,刷了\(k\)次,刷了\(j\)个格子,第\(j\)个格子刷的是\(0\)还是\(1\)所能的到的最大涂正确的格子数


那么对于现在涂到了第\(j\)个格子,分类讨论:

  • 如果第\(j\)个格子原本应该涂成\(0\),那么我有给它涂成\(1\)和涂成\(0\)两种方案:

    如果我给它涂成\(0\),正确涂格子数肯定要\(+1\),但是上一次怎么涂的呢?我们又要分类讨论:

    1.上一次如果涂的也是\(0\),我们可以选择将这次涂格子的过程合并到上次,即仍然总共只涂了\(k\)

dp[i][j][k][0]=max(dp[i][j][k][0],dp[i][j-1][k][0]+1);

2.假设上一次仍然是涂的$0$,你说,我偏不,我要重新开始涂,这是新的开始!那么这次涂了$k$次,上次自然就是涂了$k-1$次
dp[i][j][k][0]=max(dp[i][j][k][0],dp[i][j-1][k-1][0]+1);

3.那如果上次涂的是$1$呢?你说,我想和上次合并!~~gun (ノ`Д)ノ !~~ 必然不可能,因为这次涂的是 $0$,那么这次涂了$k$次,上次自然也就是涂了$k-1$次
dp[i][j][k][0]=max(dp[i][j][k][0],dp[i][j-1][k-1][1]+1);

  • 如果第\(j\)个格子原本应该涂成\(1\),那么情况就和刚才的几乎一样啦,就是把\(0\)\(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~

屑题解

posted @ 2022-09-23 17:45  Truman_2022  阅读(40)  评论(0编辑  收藏  举报