【差分】

【差分】

板子

一维差分

//差分数组的加数操作
void insert(int l,int r,int c){
    b[l]+=c;
    b[r+1]-=c;
}
//预处理 构造差分数组
for(int i=1;i<=n;i++) insert(i,i,a[i]);

二维差分

void insert(int x1,int y1,int x2,int y2,int c){
    b[x1][y1]+=c;
    b[x2+1][y1]-=c;
    b[x1][y2+1]-=c;
    b[x2+1][y2+1]+=c;
}
//构造差分数组
for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
        insert(i,j,i,j,a[i][j]);
     }
}
//还原原数组 求二维前缀和
for(int i=1;i<=n;i++) {
    for(int j=1;j<=m;j++){
        b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
    }
}

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

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  阅读(11)  评论(0)    收藏  举报