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