562. 壁画
562. 壁画 - AcWing题库 |
---|
难度:中等 |
时/空限制:1s / 64MB |
总通过数:4154 |
总尝试数:10197 |
来源: Google Kickstart2018 Round H Problem B |
算法标签 思维题枚举前缀和 |
题目内容
Thanh 想在一面被均分为 N 段的墙上画一幅精美的壁画。
每段墙面都有一个美观评分,这表示它的美观程度(如果它的上面有画的话)。
不幸的是,由于洪水泛滥,墙体开始崩溃,所以他需要加快他的作画进度!
每天 Thanh 可以绘制一段墙体。
在第一天,他可以自由的选择任意一段墙面进行绘制。
在接下来的每一天,他只能选择与绘制完成的墙面相邻的墙段进行作画,因为他不想分开壁画。
在每天结束时,一段未被涂颜料的墙将被摧毁(Thanh 使用的是防水涂料,因此涂漆的部分不能被破坏),且被毁掉的墙段一定只与一段还未被毁掉的墙面相邻。
Thanh 的壁画的总体美观程度将等于他作画的所有墙段的美观评分的总和。
Thanh想要保证,无论墙壁是如何被摧毁的,他都可以达到至少 B 的美观总分。
请问他能够保证达到的美观总分 B 的最大值是多少。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据的第一行包含整数 N。
第二行包含一个长度为 N 的字符串,字符串由数字 0∼9 构成,第 i 个字符表示第 i 段墙面被上色后能达到的美观评分。
输出格式
每组数据输出一个结果,每个结果占一行。
结果表示为 Case #x: y
,其中 x 为组别编号(从 1 开始),y 为 Thanh 可以保证达到的美观评分的最大值。
数据范围
1
≤
T
≤
100
1≤T≤100
1≤T≤100,
存在一个测试点
N
=
5
∗
1
0
6
N=5∗10^6
N=5∗106,其他测试点均满足
2
≤
N
≤
100
2≤N≤100
2≤N≤100
输入样例:
4
4
1332
4
9583
3
616
10
1029384756
输出样例:
Case #1: 6
Case #2: 14
Case #3: 7
Case #4: 31
样例解释
在第一个样例中,无论墙壁如何被破坏,Thanh都可以获得 6 分的美观总分。在第一天,他可以随便选一个美观评分3的墙段进行绘画。在一天结束时,第一部分或第四部分将被摧毁,但无论哪一部分都无关紧要。在第二天,他都可以在另一段美观评分 3 的墙段上作画。
在第二个样例中,Thanh 在第一天选择最左边的美观评分为 9 的墙段上作画。在第一天结束时唯一可以被毁掉的墙体是最右边的那段墙体,因为最左边的墙壁被涂上了颜料。在第二天,他可以选择在左数第二段评分为 5 的墙面上作画。然后右数第二段墙体被摧毁。请注意,在第二天,Thanh不能选择绘制第三段墙面,因为它不与任何其他作画墙面相邻。这样可以获得 14 分的美观总分。
题目解析
- 每一段墙上有一个评分,有n段
- 第一天可以随便选一个位置开始
- 画的时候是连续画,不能跳着画,每次画只能在画过墙旁边画,所有画过的画是连续的一段
- 每天会画一段墙,每天结束的时候会坏一段墙
- 坏掉的墙一定只与一段还未被毁掉的墙面相邻,意思是只能从两边坏,从中间开始坏的话会与两端还未被毁掉的墙面相邻
- 当我们停下的时候,所有没画过的墙都已经毁坏掉了
一、在最坏的情况下,美观评分值的最大值是多少
数据范围最多有100个数据,其中有一个数据长度为500万,其余所有数据都很小,所有数据的总长大概为500万的级别
需要将时间复杂度控制到 O ( N ) O(N) O(N), O ( log N ) O(\log N) O(logN)长度比较小的话也能过
二、先考虑一下最终画的墙的长度是多少
当我们画完的时候,一定是留下中间画完的这一段,而且其余的墙都已经坏掉了,不会出现孤立的线段没有画也没有坏
这个过程是先画再坏,先画再坏,所以画的长度是 [ n 2 [\frac{n}{2} [2n,上取整,其余都是坏掉的
n如果是偶数的话,就是 n 2 \frac{n}{2} 2n,如果是奇数的话,除了第一个,其余的画过的和坏了的各分为 n 2 \frac{n}{2} 2n
( 1 + n 2 下取整 = n 2 上取整 1+\frac{n}{2}下取整=\frac{n}{2}上取整 1+2n下取整=2n上取整)
三、是不是所有长度是 n 2 \frac{n}{2} 2n上取整的区间都一定可以被画出来?
长度为 n 2 \frac{n}{2} 2n的区间有很多个,最开始的起点可以任意取,一共大概有 n 2 \frac{n}{2} 2n
个
先考虑能把 n 2 \frac{n}{2} 2n都取到的情况,然后在能取到的里面挑一个总和最大值,
任何一个长度为 n 2 \frac{n}{2} 2n的区间都一定可以取到
所以只需枚举所有长度为 n 2 \frac{n}{2} 2n的区间,求一下每个区间的总和,取一个最大值就可以了
证明一定可以被画出来
为了不失去一般性,任意取一个长度为
n
2
\frac{n}{2}
2n的区间,由于中间的长度为
n
2
\frac{n}{2}
2n,所以如果右边是对称过来的话,左边的两段长度也是对称的(可能有奇数偶数的问题,最多差一个)
构造一个方案,使得在最坏情况下,一定能够使这 n 2 \frac{n}{2} 2n被画到
分类讨论
1. n为奇数
a
+
b
=
n
−
1
2
a+b=\frac{{n-1}}{2}
a+b=2n−1
因为画的部分是
n
2
\frac{n}{2}
2n上取整,没有画的部分是
n
2
\frac{n}{2}
2n下取整,也就是
n
−
1
2
\frac{{n-1}}{2}
2n−1,a和b是两边没有画的长度之和
除了两个a和两个b之外,中间还有一个是空余的。
所以如果n是奇数的话,第一步先画中间这一个空余的墙,画完以后,左边剩下两个a,右边剩下两个b
天气的选择
如果天气选择从左边坏,就选择画左边,如果天气选择坏右边,就画右边
这样就能无时无刻保证,左边的长度一定是相等的,右边的长度也一定是相等的,天气从左边取一个,我们也从左边取一个,天气从右边取一个,我们也从右边取一个
最终可以从左边的2a当中取a个,从右边的两个b中取一个b,因此最终可以将a+b+1全部取到
所以如果n是奇数的话,可以全部取到
2. n为偶数
中间没有空余的一段
在2a和2b中间相邻的那两个,可以任取
如果取左边这个,左边变成了a-1,右边是两个b
天气如果坏的是左边,第二天就画左边,如果坏的是右边,第二天就画右边,右边可以保证一定取b个,左边一共是
2
a
−
1
{2a-1}
2a−1个,可以保证取到
2
a
−
1
2
\frac{{2a-1}}{2}
22a−1下取整个,就是可以取到a-1个
左边可以取a-1个,右边可以取b个,加上中间的一个,总共可以取a+b个,也是
n
2
\frac{n}{2}
2n个
这个题目不是贪心的题
贪心:在一个集合当中取一个最优解,每次可以将集合分成两类,可以证明在某一类当中一定有最优解,那我们就可以只考虑这一类,继续将剩余的类分成两大类,同样的方式可以证明,最优解一定在某一类中,就只用考虑某一类就可以了
每次保证答案一定在考虑的集合当中,而且可以不断缩小集合的范围
两种做法
只要枚举所有长度为 n 2 \frac{n}{2} 2n上取整的区间,求一下这些区间的最大和是多少
有很多种做法
可以用双指针,维护一个滑动窗口
也可以使用前缀和,可控性比较长,可以求任意区间的总和
前缀和原理
比如有一个数组,
a
1
,
a
2
…
a
n
a_{1},a_{2}\dots a_{n}
a1,a2…an,需要快速地去求若干个区间的区间和,比如求
a
l
…
a
r
a_{l}\dots a_{r}
al…ar区间的总和
可以用
O
(
1
)
O(1)
O(1)的时间复杂度,求出这个区间的总和是多少
795.前缀和
给我们一个长度是n的序列,有m个询问,每个询问给一个区间,需要求这个区间的总和是多少,可以做到
O
(
n
+
m
)
O(n+m)
O(n+m)的时间复杂度
暴力:每一个询问,枚举一下然后再算总和,最坏情况下,L取1,R取n,就是O(n),所以直接暴力做,时间复杂度是
O
(
n
∗
m
)
O(n*m)
O(n∗m).
构造一个前缀和数组,
s
0
=
0
s
1
=
a
1
s
2
=
a
1
+
a
2
…
s
i
−
1
=
a
1
+
a
2
+
…
a
i
−
1
s
i
=
a
1
+
a
2
+
⋯
+
a
i
…
s
n
=
a
1
+
⋯
+
a
n
s
i
=
s
i
−
1
+
a
i
O
(
n
)
\begin{array}{} s_{0}=0 \\ s_{1}=a_{1}\\ s_{2}=a_{1}+a_{2}\\ \dots \\ s_{i-1}=a_{1}+a_{2}+\dots a_{i-1} \\ s_{i}=a_{1}+a_{2}+\dots+a_{i} \\ \dots \\ s_{n}=a_{1}+\dots+a_{n} \\ \\ s_{i}=s_{i-1}+a_{i} \qquad O(n) \end{array}
s0=0s1=a1s2=a1+a2…si−1=a1+a2+…ai−1si=a1+a2+⋯+ai…sn=a1+⋯+ansi=si−1+aiO(n)
a
L
+
⋯
+
a
R
=
s
R
−
s
L
−
1
=
(
a
1
+
a
2
+
⋯
+
a
R
)
−
(
a
1
+
a
2
+
⋯
+
a
L
−
1
)
=
a
L
+
⋯
+
a
R
\begin{array}{} a_{L}+\dots+a_{R} \\ =s_{R}-s_{L-1} \\ =(a_{1}+a_{2}+\dots +a_{R})-(a_{1}+a_{2}+\dots+a_{L-1}) \\ =a_{L}+\dots+a_{R} \end{array}
aL+⋯+aR=sR−sL−1=(a1+a2+⋯+aR)−(a1+a2+⋯+aL−1)=aL+⋯+aR
这样,每次算前缀和,可以用
O
(
1
)
O(1)
O(1)的计算量,一共是m个询问,就是
O
(
m
)
O(m)
O(m)的计算量,所以整个的时间复杂度就从
O
(
n
m
)
O(nm)
O(nm)优化到了
O
(
n
+
m
)
O(n+m)
O(n+m)
代码
先预处理一下原数组的前缀和,枚举所有长度是 n 2 \frac{n}{2} 2n上取整的区间,用前缀和算法,去求这个区间的和,然后在所有的和当中取一个最大值,输出就可以了
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
conse int N = 5000010; //n最大值是500万
int n;
int s[N]; //定一下前缀和数组
char str[N]; //需要用一个字符串去读
//输入的时候用字符串输入,中间没有空格
int main()
{
//有多组测试数据
int T;
scanf("%d", &T); //先读取测试数据的数量
//依次枚举每一个测试数据
for (int cases = 1; cases <= T; cases ++)
{
scanf("%d", &n); //读入序列的长度
//读入字符串
scanf("%s", str + 1); //前缀和算法下标一般从1开始会好一些
//预处理前缀和
for (int i = 1; i <= n; i ++)
{
s[i] = s[i - 1] + str[i] - '0';//把字符0-9转换成数字0-9
int res = 0;
//定义一个最大值,N是500万,每个数据大小是9,总和不超过5000万,int最大时21亿左右,不会爆int
int m = (n + 1) / 2; //求一下长度
//枚举一下所有长度是m的区间
for (int i = m; i <= n; i ++)
{
res = max(res, s[i] - s[i-m]);
}
printf("Case #%d: %d\n", cases, res);
}
}
return 0;
}