很明显可以用线段树!(简直是模板题)
线段树:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define g getchar()
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
typedef long long ll;
const int N=1e5+10;
struct node{int l,r;ll c,ad;}tr[N<<2];
int n,m;
ll a[N];
void bt(int p,int l,int r)
{
tr[p].l=l;tr[p].r=r;
if(tr[p].l==tr[p].r){tr[p].c=a[l];return;}
int mid=(l+r)>>1;
bt(lc,l,mid);bt(rc,mid+1,r);
tr[p].c=tr[lc].c+tr[rc].c;
}
void wh(int p)
{
tr[lc].ad+=tr[p].ad;tr[rc].ad+=tr[p].ad;
tr[lc].c+=tr[p].ad*(tr[lc].r-tr[lc].l+1);
tr[rc].c+=tr[p].ad*(tr[rc].r-tr[rc].l+1);
tr[p].ad=0;
}
void add(int p,int l,int r,ll k)
{
if(l<=tr[p].l&&tr[p].r<=r)
{tr[p].ad+=k;tr[p].c+=k*(tr[p].r-tr[p].l+1);return;}
if(tr[p].ad)wh(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid)add(lc,l,r,k);
if(mid<r)add(rc,l,r,k);
tr[p].c=tr[lc].c+tr[rc].c;
}
ll ans;
void ask(int p,int l,int r)
{
if(l<=tr[p].l&&tr[p].r<=r){ans+=tr[p].c;return;}
if(tr[p].ad)wh(p);
int mid=(tr[p].l+tr[p].r)>>1;
if(l<=mid)ask(lc,l,r);
if(mid<r)ask(rc,l,r);
}
template<class o>
void qr(o&x)
{
char c=g;bool v=(x=0);
while(!( ('0'<=c&&c<='9') || c=='-' ))c=g;
if(c=='-')v=1,c=g;
while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
if(v)x=-x;
}
void write(ll x)
{
if(x/10)write(x/10);
putchar(x%10+'0');
}
void pri(ll x)
{
if(x<0)putchar('-'),x=-x;
write(x);puts("");
}
int main()
{
qr(n);qr(m);
for(int i=1;i<=n;i++)qr(a[i]);
bt(1,1,n);
while(m--)
{
char s[2];int l,r;ll d;
scanf("%s",s);qr(l);qr(r);
switch(s[0]){
case 'C':
qr(d);
add(1,l,r,d);
break;
case 'Q':
ans=0;
ask(1,l,r);
pri(ans);
break;
}
}
return 0;
}
树状数组:
树状数组是只能维护前缀和的,但是这题一点树状数组的痕迹都没有.QwQ
对于区间加,我们很容易联想到差分。那我们试试吧。
设
a
[
i
]
,
c
[
i
]
分
别
表
示
原
数
列
的
第
i
个
位
置
的
值
,
第
i
个
位
置
的
变
化
量
(
可
以
为
负
)
,
b
[
i
]
=
c
[
i
]
−
c
[
i
−
1
]
—
—
b
为
c
的
差
分
数
组
,
s
u
m
为
a
的
前
缀
和
设a[i],c[i]分别表示原数列的第i个位置的值,第i个位置的变化量(可以为负),b[i]=c[i]-c[i-1]——b为c的差分数组,sum为a的前缀和
设a[i],c[i]分别表示原数列的第i个位置的值,第i个位置的变化量(可以为负),b[i]=c[i]−c[i−1]——b为c的差分数组,sum为a的前缀和
那么
∑
i
=
1
x
b
[
i
]
=
c
[
x
]
\sum_{i=1}^x b[i]=c[x]
∑i=1xb[i]=c[x],于是
a
[
x
]
+
∑
i
=
1
x
b
[
i
]
=
a
[
x
]
+
c
[
x
]
(
第
i
个
位
置
的
当
前
值
)
a[x]+\sum_{i=1}^x b[i]=a[x]+c[x](第i个位置的当前值)
a[x]+∑i=1xb[i]=a[x]+c[x](第i个位置的当前值)
那么前x项的和可表示为:
s
u
m
[
x
]
+
∑
i
=
1
x
∑
j
=
1
i
b
[
j
]
=
s
u
m
[
x
]
+
∑
i
=
1
x
(
x
−
i
+
1
)
∗
b
[
i
]
=
sum[x]+\sum_{i=1}^x \sum_{j=1}^i b[j]=sum[x]+\sum_{i=1}^x(x-i+1)*b[i]=
sum[x]+∑i=1x∑j=1ib[j]=sum[x]+∑i=1x(x−i+1)∗b[i]=
s
u
m
[
x
]
+
∑
i
=
1
x
(
x
+
1
)
b
[
i
]
−
∑
i
=
1
x
i
∗
b
[
i
]
sum[x]+\sum_{i=1}^x (x+1)b[i]-\sum_{i=1}^x i*b[i]
sum[x]+∑i=1x(x+1)b[i]−∑i=1xi∗b[i]
(
这
样
前
缀
和
(
两
个
Σ
内
的
)
就
不
方
便
用
树
状
数
组
维
护
,
因
为
前
缀
和
中
有
与
i
无
关
的
项
(
x
+
1
)
,
所
以
我
们
需
要
把
x
+
1
提
出
来
)
(这样前缀和(两个\Sigma内的)就不方便用树状数组维护,因为前缀和中有与i无关的项(x+1),所以我们需要把x+1提出来)
(这样前缀和(两个Σ内的)就不方便用树状数组维护,因为前缀和中有与i无关的项(x+1),所以我们需要把x+1提出来)
s
u
m
[
x
]
+
(
x
+
1
)
∑
i
=
1
x
b
[
i
]
−
∑
i
=
1
x
i
∗
b
[
i
]
sum[x]+(x+1)\sum_{i=1}^x b[i]-\sum_{i=1}^x i*b[i]
sum[x]+(x+1)∑i=1xb[i]−∑i=1xi∗b[i]
那么我们用一个数组维护
b
[
i
]
b[i]
b[i]的前缀和,一个数组维护
b
[
i
]
∗
i
b[i]*i
b[i]∗i的前缀和就行.
(代码中具体为
c
1
,
c
2
c1,c2
c1,c2)
不禁感叹这种思路的巧妙
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define gc getchar()
#define ll long long
#define TP template<class o>
using namespace std;
const int N=1e5+10;
TP void qr(o&x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-') f=-1; c=gc;}
while(isdigit(c))x=x*10+c-'0',c=gc;
x*=f;
}
TP void qw(o x) {
if(x<0) putchar('-'),x=-x;
if(x/10) qw(x/10);
putchar(x%10+'0');
}
TP void pr2(o x) {qw(x); puts("");}
int n,m;
ll a[N],c1[N],c2[N];
void add(int x,ll y) {
for(ll z=x*y;x<=n;x+=x&-x)
c1[x]+=y,c2[x]+=z;
}
void add(int l,int r,ll d) {add(l,d); add(r+1,-d);}
ll ask(int x) {
ll s=0;
for(ll y=x+1;x;x&=x-1) s+=y*c1[x]-c2[x];
return s;
}
ll ask(int l,int r) {return ask(r)-ask(l-1)+a[r]-a[l-1];}
int main() {
qr(n); qr(m);
for(int i=1;i<=n;i++) qr(a[i]),a[i]+=a[i-1];
while(m--) {
char s[5];int l,r,d;
scanf("%s",s); qr(l); qr(r);
if(s[0]=='C') qr(d),add(l,r,d);
else pr2(ask(l,r));
}
return 0;
}
分块
之前我们学
B
S
G
S
(
A
x
≡
B
(
m
o
d
C
)
)
BSGS(A^x≡B(mod~~C))
BSGS(Ax≡B(mod C))就用到了分块的思想.
以后我们学莫比乌斯反演的时候,会用到整除分块——一种十分巧妙的方法。
现在我们来做算法进阶给出的分块模板题。
分块是一种用空间换取时间,达到时空平衡的“朴素算法”。效率往往比不上树状数组与线段树,但是它更加通用,容易实现。大部分常见的分块思想都可以用“大段维护,局部朴素”来形容
我们把数列A分成若干块长度不大于
s
q
r
t
(
n
)
sqrt(n)
sqrt(n)的块,其中第i块的左端点为
(
i
−
1
)
∗
s
q
r
t
(
n
)
(i-1)*sqrt(n)
(i−1)∗sqrt(n),右端点为
i
∗
s
q
r
t
(
n
)
i*sqrt(n)
i∗sqrt(n).
对于大段操作,我们直接给段打上标记.至于小段,直接暴力.
代码:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define g getchar()
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int T=333;//段数
ll a[N],sum[T],add[T];
int L[T],R[T],n,m,t,pos[N];
void change(int l,int r,ll d)//大段维护,小段朴素
{
int p=pos[l],q=pos[r];
if(p==q)
{
for(int i=l;i<=r;i++)a[i]+=d;
sum[p]+=d*(r-l+1);
}
else
{
for(int i=p+1;i<=q-1;i++)add[i]+=d;
for(int i=l;i<=R[p];i++)a[i]+=d;
sum[p]+=d*(R[p]-l+1);
for(int i=L[q];i<=r;i++)a[i]+=d;
sum[q]+=d*(r-L[q]+1);
}
}
ll ask(int l,int r)
{
int p=pos[l],q=pos[r];
ll ans=0;
if(p==q)
{
for(int i=l;i<=r;i++)ans+=a[i];
ans+=add[p]*(r-l+1);
}
else
{
for(int i=p+1;i<=q-1;i++)
ans+=sum[i]+add[i]*(R[i]-L[i]+1);
for(int i=l;i<=R[p];i++)ans+=a[i];
ans+=add[p]*(R[p]-l+1);
for(int i=L[q];i<=r;i++)ans+=a[i];
ans+=add[q]*(r-L[q]+1);
}
return ans;
}
template<class o>
void qr(o&x)
{
char c=g;bool v=(x=0);
while(!( ('0'<=c&&c<='9') || c=='-' ))c=g;
if(c=='-')v=1,c=g;
while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
if(v)x=-x;
}
void write(ll x)
{
if(x/10)write(x/10);
putchar(x%10+'0');
}
void pri(ll x)
{
if(x<0)putchar('-'),x=-x;
write(x);puts("");
}
int main()
{
qr(n);qr(m);
for(int i=1;i<=n;i++)qr(a[i]);
//分块
t=sqrt(n*1.0);
for(int i=1;i<=t;i++)
{
L[i]=R[i-1]+1;
R[i]=i*t;
}
if(R[t]<n)t++,L[t]=R[t-1]+1,R[t]=n;
//预处理
for(int i=1;i<=t;i++)
for(int j=L[i];j<=R[i];j++)
pos[j]=i,sum[i]+=a[j];
//指令
while(m--)
{
char s[2];scanf("%s",s);
int l,r;ll d;qr(l);qr(r);
switch(s[0]){
case 'C':
qr(d);change(l,r,d);
break;
case 'Q':
pri(ask(l,r));
}
}
return 0;
}