bzoj 1296 & 洛谷4158 [SCOI2009]粉刷匠 题解

题意简述

一个$n\times m$的矩阵,每个位置珂能是粉色(0表示)或者是蓝色(1表示),然后你珂以对同一行里连续一段长度的区间染上一种颜色(覆盖型),你能染$t$次,每次不限长度。求你染到的正确的颜色的个数最多是多少。

思路框架

$f[i][j]$表示前$i$行染$j$次最大染色个数。$g[i][j][k]$表示第$i$行染$j$次只考虑前$k$个格子最大方案。

具体思路

$DP$题分两步,设状态,求转移。

多数题目是状态不好设的。像这个题就是,我想了好久没想出来状态怎么设,看一下题解知道状态怎么设之后就是秒切。那么,我们这个题也是分两块来讲。

Part 1. 状态

首先$f$是很好想的。答案就是$f[n][i] (i\in [1,t])$中的最大值。

然后我们来求$f[i][j]$的转移。求着求着发现,我们先要枚举一个$k$,$k$小于$j$。转移的一部分就是$f[n-1][j-k]$,就是上一行染$j-k$次。还有$k$次留给这一行。所以我们要加上一个数,这个数就是:第$i$行中染$k$次正确染色的最大值。

然后我们来求这个子问题。

Part 1.1 子问题的状态

设$g[i][j]$表示第$i$行染$j$次的答案。考虑转移。。。转你妈,缺状态。观察数据也能轻易发现,应该是$O(nmt)$的算法,然后$O(nt)$过是什么鬼,肯定还缺一维。

缺什么状态呢。我们发现,还和染到了那里有关。不妨再加上一个$k$,表示我们考虑到第$k$个位置。

Part 1.2 子问题的转移

然后就来推转移了。像这种有“考虑到第xxx个位置”为一个状态的$DP$,通常我们用枚举断点作转移。枚举断点$q$(我真的没名字了,$i,j,k$都用过了,就叫$q$了,随便一点)。由定义,$q$小于$k$。还有一个注意点,$q$是能取$0$的。其原因是,我习惯枚举的是$q$把区间分成$[0,q]$和$[q+1,k]$。显然,$q$取$0$时也有意义。

然后我们要看$[q+1,k]$中是蓝色多还是粉色多,哪个多我们就染那种颜色。这样显然是对的。然后我们只要维护一个前缀和出来,维护每一行内蓝色的前缀和,记为$sum$。看是 $sum[i][k]-sum[i][q]$大,还是$(k-q)-(sum[i][k]-sum[i][q])$大。设这两个的最大值为$S$。

那么$g[i][j][k]=max(g[i][j-1][q]+S)$。

Part 1.3 时间复杂度?

看起来状态数是$O(nmt)$,转移数是$O(m)$,复杂度$O(nm^2t)$。但是,$g$的第二维,即染了$j$次那一维中,显然$j$不会超过$m$。因为这一行最多就$m$个数,染$m$次啥事都解决了。所以复杂度应该是$O(nm^3)$,即$O(50^4)$。稳过。

子问题完毕

Part 2. 转移

好了,回到主线。有了$g$之后,就珂以得到$f$的转移:

$f[i][j]=max(f[i-1][j-k]+g[i][k][m]) $(哎哟这个式子不要太显然,我真的不想解释了)

($Part2$比$Part1$短很多,说明这个题重点在设状态,是个毒瘤题)

代码 (精髓)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
#define N 61
#define T 2602
#define F(i,l,r) for(int i=l;i<=r;++i)
#define D(i,r,l) for(int i=r;i>=l;--i)
#define Fs(i,l,r,c) for(int i=l;i<=r;c)
#define Ds(i,r,l,c) for(int i=r;i>=l;c)
#define Tra(i,u) for(int i=G.Start(u),__v=G.To(i);~i;i=G.Next(i),__v=G.To(i))
#define MEM(x,a) memset(x,a,sizeof(x))
#define FK(x) MEM(x,0)

int n,m,t;
int sum[N][N];char mp[N][N];
void R1(int &x)
{
x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=(f==1)?x:-x;
}
void Input()
{
R1(n),R1(m),R1(t);
F(i,1,n)
{
scanf("%s",mp[i]+1);
sum[i][0]=0;
F(j,1,m)
{
if (mp[i][j]=='1') sum[i][j]=sum[i][j-1]+1;
else sum[i][j]=sum[i][j-1];
}
}
}

int f[N][T],g[N][T][N];
void Soviet()
{
F(i,1,n) F(j,1,t) F(k,1,m) F(q,0,k-1)
{
int ss=sum[i][k]-sum[i][q];
int S=max(ss,(k-q)-ss);
g[i][j][k]=max(g[i][j][k],g[i][j-1][q]+S);
}

F(i,1,n) F(j,1,t) F(k,0,min(j,m)) f[i][j]=max(f[i][j],f[i-1][j-k]+g[i][k][m]);
printf("%d\n",*max_element(f[n]+1,f[n]+t+1));
}

#define Flan void
Flan IsMyWife()
{
Input();
Soviet();
}
}
int main()
{
Flandre_Scarlet::IsMyWife();
getchar();getchar();
return 0;
}
w