[Manthan, Codefest 18][Codeforces 1037F. Maximum Reduction]
题目链接:1037F - Maximum Reduction
题目大意:给出一段代码,给你一个长度为n的数组和数字k,求程序运行结果,mod 1e9+7输出
简单翻译下代码的意思,初始定义一个空数组b,分别查询区间[1,k];[2,k+1];...;[n-k+1,n]的最大值,并将这 n-k+1 个区间最大值放入b,将b中元素之和加入到ans里,并把这个长度为n-k+1的数组b放入到下一层的递归,这样就要再求n-2k+1次、n-3k+1次最大值,直到数组的长度小于k(即无法求区间长度为k的最大值)
题解:我一开始也不知道从何入手,于是就写了下当n=10,k=3的时候要求的是哪些区间最大值的和,写出来是这样
[1,3][2,4][3,5][4,6][5,7][6,8][7,9][8,10]
[1,5][2,6][3,7][4,8][5,9][6,10]
[1,7][2,8][3,9][4,10]
[1,9][2,10]
可以发现当左端点 l 固定的时候,右端点只可能是 l+t(k-1),称这样的区间是有用的区间
然后我们再来考虑另外一件事情,对于上述的每一个区间,这些区间的最大值都是这些区间中的某一个数【废话←_←】
于是我们就可以开始考虑每个数被当做区间最大值的次数是多少这个问题。对于任意一个位置x上的数,他都是有一个影响范围的,即在这个范围里,任意一个包含x的区间,a[x]都是这个区间内的最大值。因此我们要先预处理出每个数的左边最后一个比他小的数是多少,他右边最后一个比他小的数是多少,记为l[i],r[i],显然区间[ l[i],r[i] ]是a[i]这个数的影响范围。这时对任意的x,我们只需要求出在区间[ l[x],r[x] ]内,有多少个有用的区间包含x
那怎么计算有用区间的个数呢?这就需要用到之前求出的性质了。显然一个区间[l,r]有用,当且仅当 (r-l)=t(k-1) 其中t为正整数。因此区间[l,r]中以l为左端点的有用的区间个数为 \(\left \lfloor \frac{r-l}{k-1} \right \rfloor\) ,故区间[l,r]中的有用区间数为 \(\sum_{i=l}^{r}\left \lfloor \frac{r-i}{k-1} \right \rfloor\) 。但注意到,这些区间里有一些是没有包含到x的,所以若设g(l,r,x)表示区间[l,r]中包含x的有用区间数,则$$g(l,r,x)=\sum_{i=l}^{x}\left \lfloor \frac{r-i}{k-1} \right \rfloor - \sum_{i=l}^{x-1}\left \lfloor \frac{x-1-i}{k-1} \right \rfloor$$
这个式子表示的就是左端点在x及x左边的有用区间总数 减去 右端点小于x的有用区间总数。这样循环一遍的复杂度是O(2(x-l)),也就是说如果直接暴力求解的话是铁定会超时的,所以我们需要进一步优化
upd:看到这里可以直接点开评论区第一条回复,用预处理前缀和优化即可
这里优化的姿势有两种,一种是考虑这个式子可以转换为若干个等差数列之和,直接套公式就好,这种做法写起来简单但需要一定时间思考,当时做这道题的时候懒得细想就用了另一种方法
第二种方法就是用类欧几里得算法做了,由于本人比较菜,对这方面的知识不太了解,也只会套模板,就不详细介绍了_(:з」∠)_这个相关的知识点百度一下都有的233333
类欧几里得算法可以对 \(f(a,b,c,n)=\sum_{i=0}^{n}\left \lfloor \frac{ai+b}{c} \right \rfloor\)这样的函数进行复杂度为O(log n)的计算,可以发现\(g(l,r,x)=f(1,r-x,k-1,x-l)-f(1,0,k-1,x-l-1)\)。于是将之前求得的式子对着板子套进去就可以快速求解了~
#include<bits/stdc++.h> using namespace std; #define N 1000001 #define LL long long #define MOD 1000000007 LL n,k,a[N],l[N],r[N],ans; LL S(LL k){return (k*(k+1)/2ll)%MOD;} LL f(LL a,LL b,LL c,LL n) { if(!a)return 0; if(a>=c || b>=c) return ((a/c)*S(n)%MOD+(n+1)*(b/c)%MOD+f(a%c,b%c,c,n))%MOD; LL m=(a*n+b)/c; return (m*n%MOD-f(c,c-b-1,a,m-1)+MOD)%MOD; } LL get(LL l,LL r,LL x) { return (f(1,r-x,k,x-l)-f(1,0,k,x-l-1)+MOD)%MOD; } int main() { scanf("%I64d%I64d",&n,&k); for(LL i=1;i<=n;i++) scanf("%I64d",&a[i]); l[1]=1,r[n]=n; for(LL i=2;i<=n;i++) { LL _=i; while(_>1 && a[i]>=a[_-1]) _=l[_-1]; l[i]=_; } for(LL i=n-1;i>=1;i--) { LL _=i; while(_<n && a[i]>a[_+1]) _=r[_+1]; r[i]=_; } k--; for(LL i=1;i<=n;i++) ans+=get(l[i],r[i],i)*a[i]%MOD,ans%=MOD; printf("%I64d\n",ans); return 0; }
可以发现由于这里的套模板的a为1,所以最多递归两次就能得到答案,因此这部分的时间复杂度可以看成O(n)
另外还有一个注意事项,就是在处理数组l,r的时候要半开半闭地处理,即往右边是找小于等于,往左边是找小于这样,否则当出现重复的数字的时候会重复计算答案