LYOI#1166. Cat function(线段树)
Cat function
题目描述:
分析:
操作1:将单点覆盖为k
操作2:求区间内任意两个数乘积的总和。
对于操作1:线段树的模板操作,进行单点修改即可。
对于操作2:显然只有一个或两个数的小区间计算答案非常简单,所以直接考虑线段树两个子区间之间的信息将其合并。
我们设每一个区间的乘积总和为该区间的\(ans\)。
显然两个子区间的\(ans\)之和并不等于大区间的\(ans\)。
举例:
有这两个子区间需要合并
\([1,2][3,4]\)
左区间\(ans_l=1*2=2\)
右区间\(ans_r=3*4=12\)
大区间\(ans_s=1*2+1*3+1*4+2*3+2*4+3*4=35\)
\(所以显然ans_l+ans_r \neq ans_s\)
那么我们在计算合并的\(ans\)时漏掉了什么?
通过比较上面的三个式子不难看出,在计算大区间\(ans\)时比两个子区间多计算了\(1*3\) , \(1*4\) , \(2*3\) , \(2*4\)
而这些情况正是:要计算乘积的两个元素不在同一区间内,所以无法被两个子区间\(ans\)包含。也可以理解为两个子区间的元素需要相互交叉相乘。
这里我们可以通过在原先\(ans\)的基础上加上两个子区间的和的乘积,用来概括刚才没有考虑的部分。
具体代码实现:
代码:
#include<bits/stdc++.h>
#define mod 1000000007
#define MAXN 1000086
#define int long long
using namespace std;
struct T{
int l,r,val;
int ans;
}t[MAXN];
int a[MAXN];
void update(int node){
t[node].val=(t[node<<1].val+t[node<<1|1].val)%mod;
t[node].ans=(t[node<<1].ans+t[node<<1|1].ans)%mod;
t[node].ans=(t[node].ans+(t[node<<1].val*t[node<<1|1].val)%mod)%mod;
}
void build(int l,int r,int node){
t[node].l=l;
t[node].r=r;
if(l==r){
t[node].ans=0;
t[node].val=a[l];
return;
}
int mid=(l+r)>>1;
build(l,mid,node<<1);
build(mid+1,r,node<<1|1);
update(node);
}
T ask(int l,int r,int node){
if(l<=t[node].l&&t[node].r<=r){
return t[node];
}
int mid=(t[node].l+t[node].r)>>1;
T lson={0,0},rson={0,0},kkk={0,0};
if(l<=mid){
lson=ask(l,r,node<<1);
}
if(r>mid){
rson=ask(l,r,node<<1|1);
}
kkk.val=(lson.val+rson.val)%mod;
kkk.ans=((lson.ans+rson.ans)%mod+(lson.val*rson.val)%mod)%mod;
return kkk;
}
void change(int p,int k,int node){
if(t[node].l==t[node].r){
t[node].val=k;
return ;
}
int mid=(t[node].l+t[node].r)>>1;
if(p<=mid)
change(p,k,node<<1);
else
change(p,k,node<<1|1);
update(node);
}
int n,m,op;
signed main(){
freopen("segment.in", "r", stdin);
freopen("segment.out", "w", stdout);
scanf("%lld%lld",&n,&m);
for (int i = 1; i <= n; i++) {
scanf("%lld",&a[i]);
}
build(1,n,1);
for(int i=1;i<=m;i++){
int x,y;
cin>>op>>x>>y;
if(op==1){
change(x,y,1);
}
else{
int cat=ask(x, y, 1).ans;
printf("%lld\n",cat);
}
}
return 0;
}
猫猫函数