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; }
来源:https://www.cnblogs.com/fisherss/p/12287606.html