算法学习-根号平衡(发现网上讲这个的博客和题不多)

这个东西刷新了我对根号算法的认识
(以前的认识仅仅停留在分块和莫队)

简介

什么是根号平衡?
就是当查询/修改不好维护时,将其按照是否大于 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 knn ,所以可以直接暴力加
但这个时候就必须有能支持 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[i1]
∑ 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=1xA[i]=i=1xj=1iD[j]=i=1xD[i]×(xi+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=1xA[i]=(x+1)×i=1xD[i]i=1xi×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 kn
对于每个k维护前缀和 s u m [ k ] [ x ] sum[k][x] sum[k][x]
表示模k所得的前x个值的前缀和
因为 x < k ≤ n x< k \leq \sqrt n x<kn
所以修改的复杂度是 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值