【差分】

【差分】

一般用来处理区间加减同一个数的问题

Sums of Sliding Window Maximum

https://atcoder.jp/contests/abc407/tasks/abc407_f
有关ax+b的斜率差分:两次差分+两次前缀和->优化到O(n)

int n;
bool cmp(PII x,PII y){
    if(x.first!=y.first) return x.first<y.first;
    return x.second<y.second;
}
/*
(1)求单点满足条件的连续区间:set+从大到小遍历找左右端点 O(nlogn)
(2)处理ax+b的线性分段函数的各点值:两次差分+两次前缀和(斜率、值)O(n)
*/
void solve(){
    cin>>n;
    //长度为k的子数组的贡献
    vector<ll> ans(n+1,0);
    vector<PII> a;
    for(int i=0;i<n;i++){
        ll num;
        cin>>num;
        a.push_back({num,(ll)i});
    }
    
    sort(a.begin(),a.end(),cmp);
    array<vector<ll>,3> pre;//两层差分+两层前缀和
    pre[0].resize(n+5,0);//注意2+xmin+xmax+1:数组长度会大于n+2
    pre[1].resize(n+5,0);
    pre[2].resize(n+5,0);

    set<ll> pos={-1,n};
    /*
    for(int i=1;i<=n;i++){
        cout<<pre[0][i]<<" "<<pre[1][i]<<" "<<pre[2][i]<<endl;
    }
    */
    
    //从最大数往前遍历 找到左右【自己】可以作为最大数的区间
    for(int i=n-1;i>=0;i--){
        ll val=a[i].first;
        ll idx=a[i].second;

        //在set里的都是>val的下标
        auto ripos=pos.lower_bound(idx);//右边最近的比自己大的数下标
        auto lepos=ripos;lepos--;//左边最近的比自己大的数下标
        ll r=*ripos-idx-1;
        ll l=idx-*lepos-1;

        ll xmin=min_(l,r),xmax=max_(l,r);
        //cout<<val<<" "<<idx<<" "<<xmin<<" "<<xmax<<endl;

        //差分过程:对斜率进行差分
        /*注意这边两个下标都要+1:
        (1)l:第一个点不累计
        (2)r:差分要在r+1上操作
        */
        //正斜率
        pre[0][1]+=val;
        pre[0][1+xmin+1]-=val;
        //负斜率
        pre[0][1+xmax+1]-=val;
        pre[0][2+xmin+xmax+1]+=val;

        pos.insert(idx);
    }
    //第一遍前缀和:复原斜率
    for(int i=1;i<=n+2;i++){
        pre[1][i]=pre[0][i]+pre[1][i-1];
    }
    //第二遍前缀和:复原值
    for(int i=1;i<=n+2;i++){
        pre[2][i]=pre[1][i]+pre[2][i-1];
    }
    for(int i=1;i<=n;i++){
        ans[i]=pre[2][i];
    }
    for(int i=1;i<=n;i++){
        cout<<ans[i]<<endl;
    }
}
posted @ 2025-05-28 00:03  White_ink  阅读(8)  评论(0)    收藏  举报