首先声明一下,线段树做法对于本题而言无论是复杂度还是代码长度都劣于前缀和+dp做法。笔者也已经写了前缀和dp的题解:https://blog.nowcoder.net/n/1042c69bd06d40b98f69daf8f708b28b
之所以写线段树做法,一是因为自己的数据结构很弱,需要练习(本题的线段树调试的时候发现各种bug,说明不熟练Orz),二是给希望学习线段树的童鞋们一个参考,本题是个不错的练习对象,值得去写一下。
先介绍一下线段树。(已经掌握线段树的可以跳过)。
线段树是一棵用来表示数组的区间的二叉树,每一个节点都表示原数组中的一个区间。对于一个数组而言,二叉树的根代表 区间。对于每一个节点 ,如果它所对应的区间是 ,那么它的左儿子对应的区间为 ,右儿子对应的区间为
这个二叉树可以用数组进行表示。假设 代表根节点, 对于 节点而言,它的左节点是 ,右节点是 ,那么就可以用一个结构体数组存所有的节点了。
这种线段树的好处在于,可以在节点维护各种东西,例如区间和、区间最大值、区间gcd等等,这样在构建之后可以做到 查询。
对于本题而言,由于是取两个区间,在遍历左区间的位置时求右区间和的最大值,可以用线段树做到 查询,总复杂度
代码如下:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll sum[422222],a[422222];
struct stree{
    int p,l,r;
    ll ma;
};
stree t[800020];
void build(int p,int l,int r){        //递归建立线段树
    t[p].l=l,t[p].r=r;                //线段树的区间左端点和右端点赋值。
    if(l==r){t[p].ma=a[l];return;}
    int mid=l+r>>1;
    build(p*2,l,mid);                //递归左儿子
    build(p*2+1,mid+1,r);            //递归右儿子
    t[p].ma=max(t[2*p].ma,t[2*p+1].ma);    //区间最大值赋值。
}
ll ask(int p,int l,int r){    //在节点p对应区间找原数组里l到r的最大值
    if(t[p].l>=l&&t[p].r<=r)return t[p].ma;    //若p对应区间被包含在[l,r]区间里,则直接返回。
    int mid=t[p].l+t[p].r>>1;
    ll ma=-1e18;
    if(l<=mid)ma=max(ma,ask(p*2,l,r));    //递归找左节点
    if(r>mid)ma=max(ma,ask(p*2+1,l,r));    //递归找右节点
    return ma;
}
int main(){
    int t;
    cin>>t;
    while(t--){
        int n,k,i,x;
        ll s=0;
        cin>>n>>k;
        for(i=1;i<=n;i++){
            scanf("%d",&x);
            s+=x;
            sum[i]=s;
        }
        for(i=1;i<=n-k+1;i++){
            a[i]=sum[i+k-1]-sum[i-1];
        }
        build(1,1,n-k+1);
        ll ma=-1e18;
        for(i=1;i<=n-2*k+1;i++){
            ma=max(ma,a[i]+ask(1,i+k,n-k+1));
        }
        cout<<ma<<endl;
    }
}