分析:
再次出现F题比E题简单。。。。
这题其实非常的水,那个代码的意义就是:在a序列中,找到所有大小为 k,2k−1,3k−1,…… k , 2 k − 1 , 3 k − 1 , … … 的区间,再将每个区间内的最大值加起来。
然后很容易想到算贡献,对于一个数 ai a i ,找到它左边第一个不小于它的位置 li l i ,以及它右边第一个大于它的位置 ri r i ,在这两个位置之间,且包含了i的区间,一定是以 ai a i 为它的值。
找这两个位置是单调栈的基本操作,这里就不赘述了。
然后就可以启发式合并,对每个位置i,分为左右两段:
[li,i],[i,ri]
[
l
i
,
i
]
,
[
i
,
r
i
]
枚举这两段中较小的一段中每个位置
j
j
,然后算一端为,一端在另一段中的区间有多少,再乘上
ai
a
i
作为贡献加进去。然后就可以过了
额,感觉这样有点不友好,我还是写写这玩意怎么就是启发式合并了:
首先,把大小关系定义为:如果
ai大于aj
a
i
大
于
a
j
,满足
ai>aj或(ai=aj且i<j)
a
i
>
a
j
或
(
a
i
=
a
j
且
i
<
j
)
,即相等的靠前的更大。
这样一来所有点都不可能相等了。
找到全局最大的一个点,视为根,它一定覆盖了整个序列。剩下的点覆盖的区间一定不会越过这个根。然后再在左侧找一个最大点,右侧找一个最大点,将它们设为根的左右儿子,这样依次递归下去,最后就能建成一颗二叉树。
用启发式合并的思维来看,每次将当前子树的所有元素,贡献到它的父亲那里去,然后和它的兄弟的点数比大小,较小的一个可以支持线性操作。这实质上就是上面的操作。所以这就是启发式合并。。。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define SF scanf
#define PF printf
#define MAXN 1000010
#define MOD 1000000007
using namespace std;
typedef long long ll;
int n,k;
ll ans;
ll a[MAXN];
int l[MAXN],r[MAXN];
ll st[MAXN];
int top;
int main(){
SF("%d%d",&n,&k);
for(int i=1;i<=n;i++)
SF("%I64d",&a[i]);
for(int i=1;i<=n;i++){
while(top>0&&a[st[top]]<a[i])
top--;
l[i]=st[top]+1;
st[++top]=i;
}
top=0;
st[top]=n+1;
for(int i=n;i>0;i--){
while(top>0&&a[st[top]]<=a[i])
top--;
r[i]=st[top]-1;
st[++top]=i;
}
for(int i=1;i<=n;i++){
if(i-l[i]<r[i]-i){
for(int j=l[i];j<=i;j++){
int minl=i-j;
int maxl=r[i]-j+1;
minl=max(minl,k-1);
if(minl<maxl){
ans+=1ll*(max(0,(maxl-1))/(k-1)-max(0,(minl-1))/(k-1))*a[i];
ans%=MOD;
}
}
}
else{
for(int j=i;j<=r[i];j++){
int minl=j-i;
int maxl=j-l[i]+1;
minl=max(minl,k-1);
if(minl<maxl){
ans+=1ll*(max(0,(maxl-1))/(k-1)-max(0,(minl-1))/(k-1))*a[i];
ans%=MOD;
}
}
}
}
PF("%I64d",ans);
}