http://acm.hdu.edu.cn/showproblem.php?pid=5662
1 3 2 3 1
10HintWhen and only when l=1,r=2,k=1, the value of the function is the maximum.
官方题解:
先枚举k,将所有Ai×k(i是正整数且i×k<=n)取下来存到Bi,于是将原问题转化成了下述问题:对于给定的正整数序列
B1,B2,...,B⌊kn⌋,求出连续的一段,使得这段的和值乘以这段的最小值的结果最大。
我们可以枚举最小值,设其在第i位出现,此时我们只要和值最大就可以了。设Bi向左第一个小于Bi的数是l(若没有则l=0),向右第一个小于Bi的数是r(若没有则r=⌊kn⌋+1)。
则保证最小值不变的最大和值是Bl+1,Bl+2...Br−1这段。可以使用单调栈这种数据结构来在O(⌊kn⌋)的复杂度下计算对于每个i的l和r,枚举最小值出现位置及更新答案的复杂度也是
O(⌊kn⌋)。
再考虑外层的枚举k这一部分,总复杂度为O(1+2n+...+nn)即O(n×(1+21+...+n1)),由于调和级数1+21+...+n1为logn级别,因此时间复杂度为O(nlogn)。
做了这题,还学习了单调栈的使用及其用途
#include <cstdio> #include <algorithm> using namespace std; struct Node { int index,sta; }s[300005]; int a[300005],b[300005],sta[300005],des[300005],n,sqrtK,cnt,top; long long sum[300005],ans;//sum[i]为b数组中区间[1,i]的和 int main() { int T; scanf("%d",&T); while(T-->0) { scanf("%d",&n); for(int i=1;i<=n;++i) { scanf("%d",a+i); } ans=0; for(int k=1;k<=n;++k) {//枚举k sum[0]=cnt=0; for(int i=k;i<=n;i+=k) {//找到所有的a[l*k],l*k<=n b[++cnt]=a[i]; sum[cnt]=sum[cnt-1]+b[cnt]; } b[0]=b[++cnt]=-1;//令最后一个元素的下一个高度为-1,避免循环完毕后还要弹出栈中所有元素 s[0].index=s[0].sta=0; top=0; for(int i=1;i<=cnt;++i) { if(b[i]>=b[s[top].index]) {//若当前元素大于等于栈顶元素 ++top; s[top].index=s[top].sta=sta[i]=i;//其起始下标就是自己的下标 } else { while(b[i]<b[s[top].index]) { des[s[top].index]=i-1;//获得以b[s[top].index]为最小值的区间结束的下标 --top;//弹出栈顶元素 } s[++top].index=i;//其起始下标是弹出的最后一个元素的起始下标 sta[i]=s[top].sta;//获得以b[s[top].index]为最小值的区间开始的下标 } } sqrtK=sqrt(k); for(int i=1;i<=cnt;++i)//枚举最小值 ans=max(ans,1LL*(sum[des[i]]-sum[sta[i]-1])*b[i]*sqrtK); } printf("%I64d\n",ans); } return 0; }