专辑:海量集训-题解
注:对海亮信息集训A-B班-分治初步专项的补充 考题第五题:祭祀咒语
目录:
题目描述
Menhera 酱(メンヘラちゃん)最近找到了上古祭师VHOS 留下的一本祭祀古籍,在Opportunity Fan 的帮助下她读懂并学会了某种神奇祭祀方法。
首先她需要一个长度为 k 的随机数序列作为咒语的源头,我们称之为源,然后Menhera 酱需要找出源序列的所有连续子序列的特定长度的法术值最大的序列并按长度顺序输出。
首先我们定义一个源序列的连续子序列(包括它自己)的两个参数:
1)序列中所有元素的最小值为α;
2)序列中所有元素的最大值为β;
我们定义该序列的法术值为α乘以β的乘积值。
由于我们的 Menhera 酱还需要为广大表情包事业做出贡献,于是最后一步,提交长度从 1~k 的最大法术值序列的任务就被 Menhera 扔给了你~
输入格式
注意:每个测试点内采用多组数据进行测试;
每组数据第一行一个整数 k,源序列的长度;
接下来 k 行,第 i+1 行(i∈Z)一个整数,表示源序列的第 i 个元素Ai。
输出格式
输出共 k 行,每行一个整数,按从子序列长度 1~k 的顺序输出每种子序列的集合中的法术值最大的那个序列的法术值。
样例数据
input1
5
1
6
2
4
4
output1
36
16
12
12
6
样例解释
下标从 1 开始。
长度为 1 的最大法术值区间为 2~2,答案为 6∗6=36;
长度为 2 的最大法术值区间为 4~5,答案为 4∗4=16;
长度为 3 的最大法术值区间为 2~4,答案为 2∗6=12;
长度为 4 的最大法术值区间为 2~5,答案为 2∗6=12;
长度为 5 的最大法术值区间为 1~5,答案为 1∗6=06。
分析
1、20分解法
第一重循环枚举咒语的长度1~n,第二重循环枚举起始点位置,第三重循环枚举截止点位置,找出区间内的最大值和最小值,相乘得到法术值与原数比较后取较大值即可
伪代码如下:
for i=1 ~ n
for j=1 ~ n-i+1
for k=j ~ j+i-1
/*找最大值*/
/*找最小值*/
f[i]=max(f[i],最大值乘最小值)//f[i]表示长度为i的最大法术值
此解法时间复杂度约为O(n^3)
2、40分解法
我们可以用O(n^2)的时间预处理a[i][j]为区间i到j最大值,b[i][j]为i到j的最小值。那么这样上面就不必找区间最大值和最小值,即可以少一重循环,大体效率为O(n^2)
伪代码如下:
for i = 1 ~ n
for j = 1 ~ n-i+1
f[i]=max(f[i],a[j][i+j-1]*b[j][i+j-1]);
正解
这道题其实是一个能用很多算法实现的题目(例如迭代,dfs,st+单调队列+剪枝,st+单调栈,st+线段树,纯线段树等),是一道比较好的题目。
我们采用递归,类似于二分的思想来解决这道题目
我们用f[i]表示长度为i的咒语的最大法术值
我们对于一个区间l,r,每次从中寻找一个最大值maxx和最小值minn,并记录其编号。那么此时所得到的法术值就是maxx*minn,那么这个长度原本的法术值就是f[r-l+1] (r-l+1能计算出区间的长度),那么此时的法术值就是从这两个数之间取一个较大值。做完这步后,我们这个区间的目前最大的法术值就能得到。然后继续递归
这个递归并不是名义上的二分,因为我们是用最小值的点作为分割点进行递归的。即当前区间所要处理的事情处理完后,我们用最小值的标号来区分,又分为两个区间,继续做同样的事情……
例如样例:
这里还需要注意一个东西:对于一个长度k,他可能是没被更新过,或者没被更新完全。那么他的值就只能由k+1更新来,即:
正解代码
#include<bits/stdc++.h>
using namespace std;
long long f[100010];//表示长度为i为最大法术值
long long n;
long long a[100010];
void twopoint(long long l,long long r){
if (l>r) return;//不能继续分
long long maxi=l,mini=l;
for (long long i=l;i<=r;i++) maxi=(a[i]>a[maxi]?i:maxi),mini=(a[i]<a[mini]?i:mini);//找出最大值和最小值
f[r-l+1]=max(f[r-l+1],a[mini]*a[maxi]);//跟新得到的法术值比较
twopoint(l,mini-1);
twopoint(mini+1,r);
}
int main(){
freopen("mantra.in","r",stdin);
freopen("mantra.out","w",stdout);
while (scanf("%lld",&n)!=EOF){
memset(a,0,sizeof(a));
memset(f,0,sizeof(f));
for (long long i=1;i<=n;i++) scanf("%lld",&a[i]);
for (long long i=1;i<=n;i++) f[1]=max(f[1],a[i]*a[i]);//长度为1的最大法术值
twopoint(1,n);
for (long long i=n-1;i>1;i--) f[i]=max(f[i+1],f[i]);
for (long long i=1;i<=n;i++) printf("%lld\n",f[i]);
}
fclose(stdin);
fclose(stdout);
return 0;
}