这个东西刷新了我对根号算法的认识
(以前的认识仅仅停留在分块和莫队)
简介
什么是根号平衡?
就是当查询/修改不好维护时,将其按照是否大于
n
\sqrt n
n分成两类
然后对于每一类都可以在
n
\sqrt n
n时间内维护
总时间复杂度
O
(
q
n
)
O(q\sqrt n)
O(qn)
举个栗子
以一道考试题为例
这个k很难维护,所以考虑根号平衡
将k按照是否大于
n
\sqrt n
n分成两类
1:
k
>
n
k > \sqrt n
k>n
这个时候
n
k
≤
n
\frac{n}{k}\leq \sqrt n
kn≤n,所以可以直接暴力加
但这个时候就必须有能支持
O
(
1
)
O(1)
O(1)区间加的数据结构,查询只要在
O
(
n
)
O(\sqrt n)
O(n)内就行了
似乎没有这种数据结构,所以我们要来发明一个
类似于树状数组的区间修改
维护差分数组
D
[
i
]
=
A
[
i
]
−
A
[
i
−
1
]
D[i]=A[i]-A[i-1]
D[i]=A[i]−A[i−1]
则
∑
i
=
1
x
A
[
i
]
=
∑
i
=
1
x
∑
j
=
1
i
D
[
j
]
=
∑
i
=
1
x
D
[
i
]
×
(
x
−
i
+
1
)
\sum_{i=1}^x A[i]=\sum_{i=1}^x \sum_{j=1}^iD[j] =\sum_{i=1}^xD[i]\times(x-i+1)
i=1∑xA[i]=i=1∑xj=1∑iD[j]=i=1∑xD[i]×(x−i+1)
∑
i
=
1
x
A
[
i
]
=
(
x
+
1
)
×
∑
i
=
1
x
D
[
i
]
−
∑
i
=
1
x
i
×
D
[
i
]
\sum_{i=1}^x A[i]=(x+1)\times \sum_{i=1}^xD[i]-\sum_{i=1}^x i\times D[i]
i=1∑xA[i]=(x+1)×i=1∑xD[i]−i=1∑xi×D[i]
也就是说我们只需要维护
D
[
i
]
D[i]
D[i]和
i
×
D
[
i
]
i\times D[i]
i×D[i]的前缀和就可以了
这个可以简单的用分块单点加就可以了
2:
k
≤
n
k\leq \sqrt n
k≤n
对于每个k维护前缀和
s
u
m
[
k
]
[
x
]
sum[k][x]
sum[k][x]
表示模k所得的前x个值的前缀和
因为
x
<
k
≤
n
x< k \leq \sqrt n
x<k≤n
所以修改的复杂度是
O
(
n
)
O(\sqrt n)
O(n)
查询的时候枚举k,对于每个k统计和就可以了
总复杂度
O
(
q
n
)
O(q\sqrt n)
O(qn)
另外
这个题似乎也能离线做
不过在线做的时候常数大,据我测试块长取
0.7
×
n
0.7\times \sqrt n
0.7×n最好
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL N = 3e5+7;
const LL S = 800;
const double C = 1;
LL sumA[S],sumB[S];
LL valA[N],valB[N];
LL pos[N];
LL st[S],ed[S];
LL n,q;
LL m;
LL a[N];
void put(LL a)
{
cout<<a<<endl;
}
void add(LL x,LL v)
{
if(x>n) return;
x=max(x,1ll);
valA[x]+=v;
valB[x]+=x*v;
sumA[pos[x]]+=v;
sumB[pos[x]]+=x*v;
}
LL askA(LL x)
{
LL res=0;
for(LL i=st[pos[x]];i<=x;i++)
res+=valA[i];
for(LL i=1;i<pos[x];i++)
res+=sumA[i];
return res;
}
LL askB(LL x)
{
LL res=0;
for(LL i=st[pos[x]];i<=x;i++)
res+=valB[i];
for(LL i=1;i<pos[x];i++)
res+=sumB[i];
return res;
}
LL ask(LL x)
{
if(!x) return 0;
return (x+1)*askA(x)-askB(x);
}
void Add(LL k,LL l,LL r,LL x)
{
for(int i=0;i<=n;i+=k)
{
add(i+l,x);
add(i+r+1,-x);
}
}
LL sum[S][S];
void Modify(LL k,LL l,LL r,LL x)
{
LL cnt=0;
for(LL i=l;i<=r;i++)
{
cnt+=x;
sum[k][i]+=cnt;
}
for(LL i=r+1;i<k;i++)
sum[k][i]+=cnt;
}
LL Query(LL l,LL r)
{
LL ans=0;
for(LL k=1;k<=m;k++)
{
LL sk=k-1;
LL posl=l/k;
LL posr=r/k;
LL pl=l%k;
LL pr=r%k;
if(posl==posr) ans=ans+sum[k][pr]-(pl?sum[k][pl-1]:0ll);
else ans=ans+(posr-posl)*sum[k][sk]+sum[k][pr]-sum[k][pl-1];
}
return ans;
}
int ans[N];
int s[S];
struct node
{
int l,r,x,v;
}w[N];
int top=0;
int main()
{
freopen("sorrow.in","r",stdin);
freopen("sorrow.out","w",stdout);
cin>>n>>q;
m=LL(1.0*C*sqrt(n*1.0));
for(LL i=1;i<=n;i++)
{
pos[i]=(i-1)/m+1;
if(!st[pos[i]]) st[pos[i]]=i;
}
for(LL i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
add(i,a[i]);
add(i+1,-a[i]);
}
while(q--)
{
LL op,k,l,r,x;
scanf("%lld",&op);
if(op==1)
{
scanf("%lld%lld%lld%lld",&k,&l,&r,&x);
if(k>m) Add(k,l,r,x);
else Modify(k,l,r,x);
}
else
{
scanf("%lld%lld",&l,&r);
LL ans=0;
ans=ans+ask(r)-ask(l-1);
ans=ans+Query(l,r);
printf("%lld\n",ans);
}
}
return 0;
}