首页 > 技术文章 > 牛客3 F/G 牛牛的Link Power |线段树区间修改

fisherss 2020-02-09 16:43 原文

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;
}

推荐阅读