牛客3 F/G 牛牛的Link Power |线段树区间修改

F题 牛牛的Link Power I

https://ac.nowcoder.com/acm/contest/3004/F

思路

每个“1”对于它本身位置产生的影响贡献为0,而往后面依次产生了0,1,2,3,4,5...的贡献。

参考下图,对于值为1的i号点,我们把线段树叶节点维护的[i+1,n]的值都+1(区间修改);每次查询值为1的点,query(1,i),查询出i点前所有为1的点对它的能量贡献,累加就可以了。

建一颗线段树,区间修改,区间求和,累加查询i点前所有为1的点对i点的能量贡献即可。

为什么区间+1之后,可以用线段树查询i点前所有为1的点对i点的能量贡献,
因为 对于i点将[i+1,n]都加1,相当于把它影响到的点都权值+了1;
不好理解的话,就参考下图的例子

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
const ll mod = 1e9+7;
int n,q,a[maxn];
char s[maxn];
ll sumv[maxn<<2],addv[maxn<<2];

//合并 
void pushup(int o){
    sumv[o] = sumv[o<<1] + sumv[o<<1|1];
}

//建树 
void build(int o,int l,int r){
    if(l == r) { //到最后一行了 [1,1] [2,2] ... 
        sumv[o] = 0;
        return;
    }
    int mid = (l+r)>>1; 
    build(o<<1,l,mid);
    build(o<<1|1,mid+1,r);
    pushup(o);//向上合并 
}

void puttag(int o,int l,int r,ll v){
    addv[o] += v;
    sumv[o] += (r-l+1)*v;
}

void pushdown(int o,int l,int r){
    if(addv[o] == 0) return;
    addv[o<<1] += addv[o];
    addv[o<<1|1] += addv[o];
    int mid = (l+r)>>1;
    sumv[o<<1] += addv[o] * (mid-l+1);
    sumv[o<<1|1] += addv[o] * (r-mid);
    addv[o] = 0;
} 

void optadd(int o,int l,int r,int ql,int qr,ll v){
    if(ql<=l&&r<=qr){ //1.完全覆盖(l,r)这个子区间 就先更新好值,打上标记(为后面作标记准备)
        puttag(o,l,r,v); //在puttag这里更新区间的值 打上标记 
        return;
    }
    int mid = (l+r)>>1;
    pushdown(o,l,r);//标记下放 因为接下来要更新子区间了
    if(ql <= mid) optadd(o<<1,l,mid,ql,qr,v);
    if(qr > mid) optadd(o<<1|1,mid+1,r,ql,qr,v);
    pushup(o);
}

ll querysum(int o,int l,int r,int ql,int qr){
    if(ql<=l&&r<=qr) return sumv[o];
    ll ans = 0;
    int mid = (l+r)>>1;
    pushdown(o,l,r);
    if(ql<=mid) ans+=querysum(o<<1,l,mid,ql,qr);
    if(qr>mid) ans+=querysum(o<<1|1,mid+1,r,ql,qr);
    return ans;
}


int main(){
    cin>>n;
    scanf("%s",s+1);
    build(1,1,n);
    //query(i+1 ~ n) 求i点之前的所有点对它的贡献
    ll ans = 0;
    for(int i=1;i<=n;i++){
        if(s[i] == '1'){
            optadd(1,1,n,i+1,n,1); //i+1~n 区间+1 
            ans = (ans + querysum(1,1,n,1,i))%mod; //求i点之前的所有点对它的贡献
        }
    }
    cout<<ans;
    return 0;
}

G题 牛牛的Link Power II

https://ac.nowcoder.com/acm/contest/3004/G

思路

考虑这道题是F题的带修改操作,这样我们只建一颗线段树是不行的,因为我们要考虑“待修改的点”分别对它后面所有点和它后面所有点的影响
pre线段树:查询求和:i点之前的所有点对它的贡献 (i对它前面所有点的影响)
suf线段树:查询求和:i点之后的所有点对它的贡献 (i对它前面所有点的影响)

这里多了一颗suf线段树,参考F题思路和下图,再建一个后缀suf

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
const ll mod = 1e9+7;
int n,m,q,a[maxn];
char s[maxn];

struct seg_tree{
    
    ll sumv[maxn<<2],addv[maxn<<2];
    
    //合并 
    void pushup(int o){
        sumv[o] = sumv[o<<1] + sumv[o<<1|1];
    }
    
    //建树 
    void build(int o,int l,int r){
        if(l == r) { //到最后一行了 [1,1] [2,2] ... 
            sumv[o] = 0;
            return;
        }
        int mid = (l+r)>>1; 
        build(o<<1,l,mid);
        build(o<<1|1,mid+1,r);
        pushup(o);//向上合并 
    }
    
    void puttag(int o,int l,int r,ll v){
        addv[o] += v;
        sumv[o] += (r-l+1)*v;
    }
    
    void pushdown(int o,int l,int r){
        if(addv[o] == 0) return;
        addv[o<<1] += addv[o];
        addv[o<<1|1] += addv[o];
        int mid = (l+r)>>1;
        sumv[o<<1] += addv[o] * (mid-l+1);
        sumv[o<<1|1] += addv[o] * (r-mid);
        addv[o] = 0;
    } 
    
    void optadd(int o,int l,int r,int ql,int qr,ll v){
        if(ql<=l&&r<=qr){ //1.完全覆盖(l,r)这个子区间 就先更新好值,打上标记(为后面作标记准备)
            puttag(o,l,r,v); //在puttag这里更新区间的值 打上标记 
            return;
        }
        int mid = (l+r)>>1;
        pushdown(o,l,r);//标记下放 因为接下来要更新子区间了
        if(ql <= mid) optadd(o<<1,l,mid,ql,qr,v);
        if(qr > mid) optadd(o<<1|1,mid+1,r,ql,qr,v);
        pushup(o);
    }
    
    ll querysum(int o,int l,int r,int ql,int qr){
        if(ql<=l&&r<=qr) return sumv[o];
        ll ans = 0;
        int mid = (l+r)>>1;
        pushdown(o,l,r);
        if(ql<=mid) ans+=querysum(o<<1,l,mid,ql,qr);
        if(qr>mid) ans+=querysum(o<<1|1,mid+1,r,ql,qr);
        return ans;
    }

}pre,suf; 


int main(){
    cin>>n;
    scanf("%s",s+1);
    pre.build(1,1,n);
    suf.build(1,1,n);
    //pre.query(1 ~ x) 求i点之前的所有点对它的贡献 (i对它前面所有点的影响) 
    //suf.query(x ~ n) 与上面相反 
    ll ans = 0;
    //先做一次计算求所有ans 并对pre和suf线段树更新加点 
    for(int i=1;i<=n;i++){
        if(s[i] == '1'){
            ans = (ans + pre.querysum(1,1,n,1,i))%mod; //求i点之前的所有点对它的贡献
            if(i!=n) pre.optadd(1,1,n,i+1,n,1); //i+1~n 区间+1 
            if(i!=1) suf.optadd(1,1,n,1,i-1,1); 
        }
    }
    cout<<ans<<endl;
    cin>>m;
    int q,x;
    for(int i=1;i<=m;i++){
        cin>>q>>x;
        ll preSum = 0,sufSum = 0;
        preSum = pre.querysum(1,1,n,1,x); //求i前所有点 对i的贡献pre 
        sufSum = suf.querysum(1,1,n,x,n); //求i后所有点 对i的贡献pre 
        if(q == 1){
            ans = (ans + preSum + sufSum)%mod;
            if(x!=n) pre.optadd(1,1,n,x+1,n,1);
            if(x!=1) suf.optadd(1,1,n,1,x-1,1);
        }else{
            ans = ((ans - preSum - sufSum)%mod+mod)%mod;
            if(x!=n) pre.optadd(1,1,n,x+1,n,-1);
            if(x!=1) suf.optadd(1,1,n,1,x-1,-1);
        }
        cout<<ans%mod<<endl;
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/fisherss/p/12287606.html
今日推荐