莫队(分块版,不是二维曼哈顿距离什么什么最小生成树)就是分块排序优化暴力查找,减少查找区间之间的覆盖长度,从而优化时间复杂度,有一种说法很精彩
如果我们已知[l,r]的答案,能在O(1)时间得到[l+1,r]的答案以及[l,r-1]的答案,即可使用莫队算法。时间复杂度为O(n^1.5)。如果只能在logn的时间移动区间,则时间复杂度是O(n^1.5*logn)。 其实就是找一个数据结构支持插入、删除时维护当前答案。
这道题的话我们很容易用数组来实现,做到O(1)的从[l,r]转移到[l,r+1]与[l+1,r]。
那么莫队算法怎么做呢?以下都是在转移为O(1)的基础下讨论的时间复杂度。另外由于n与m同阶,就统一写n。
如果已知[l,r]的答案,要求[l’,r’]的答案,我们很容易通过|l – l’|+|r – r’|次转移内求得。
对于它的时间复杂度分析
将n个数分成sqrt(n)块。 按区间排序,以左端点所在块内为第一关键字,右端点为第二关键字,进行排序,也就是以(pos [l],r)排序
然后按这个排序直接暴力,复杂度分析是这样的:
1、i与i+1在同一块内,r单调递增,所以r是O(n)的。由于有n^0.5块,所以这一部分时间复杂度是n^1.5。
2、i与i+1跨越一块,r最多变化n,由于有n^0.5块,所以这一部分时间复杂度是n^1.5
3、i与i+1在同一块内时变化不超过n^0.5,跨越一块也不会超过n^0.5,忽略*2。由于有n个数,所以时间复杂度是n^1.5
于是就是O(n^1.5)了
这道题是比较模板的莫队分块了,对于一个区间询问[L,R],我们要求的ans是
ans=(Σsum(color[i])−1)∗sum(color[i])/2)/((R−L+1)∗(R−L))ans=(Σsum(color[i])−1)∗sum(color[i])/2)/((R−L+1)∗(R−L))
简化可得
ans=(Σ(sum(color[i])2)−(R−L+1))/((R−L+1)∗(R−L))ans=(Σ(sum(color[i])2)−(R−L+1))/((R−L+1)∗(R−L))
其中sum(color[i])sum(color[i])指第i种颜色在[L,R]中出现的次数
那么我们现在求出各个询问区间中sum(color[i])2sum(color[i])2就行了,具体实现方法参照代码
注意:
1.当一种颜色数量ci增加1时,我们可以看出ans=ans−ci2+(ci+1)2ans=ans−ci2+(ci+1)2,简化可得ans=ans+(ci∗2+1)ans=ans+(ci∗2+1),同样减少1时,ans=ans−ci2+(ci−1)2ans=ans−ci2+(ci−1)2,简化得ans=ans+(ci∗2−1)ans=ans+(ci∗2−1),这样做的好处是减少乘法且可用位运算,优化常数(然而并没有什么卵用)
文字转自:https://blog.csdn.net/xym_csdn/article/details/50889293
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
struct node
{
int l,r,id;
ll p,q;// p 分子,q 分母
}t[maxn];
int n,m,a[maxn],num[maxn],cnt[maxn],block;
// a 原数组 , num[i] i在第几块(分块) ,cnt[i] 记录颜色数
ll ans;
bool cmp1(node a,node b)
{
if(num[a.l]==num[b.l])
return a.r<b.r;
return a.l<b.l;
}
bool cmp2(node a,node b)
{
return a.id<b.id;
}
ll gcd(ll a,ll b)
{
if(!b)
return a;
return gcd(b,a%b);
}
void add(int x) //把x位置的数字移进来
{
// cnt[x]++;
// if(cnt[x]==1)
// ans++;
ans += (cnt[x]<<1)+1;
cnt[x]++;
}
void del(int x) //把x位置的数字移出去
{
// cnt[x]--;
// if(cnt[x]==0)
// ans--;
ans += 1-(cnt[x]<<1);
cnt[x]--;
}
int main()
{
int l,r;
scanf("%d%d",&n,&m);
block = sqrt(n);
for(int i=1;i<=n;i++)
scanf("%d",a+i);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&t[i].l,&t[i].r);
t[i].id = i;
num[i] = (i-1)/block+1;
}
sort(t+1,t+m+1,cmp1);//按照区间排序
l = 1 , r = 0;
for(int i=1 ; i<=m ; i++)
{
if(t[i].l == t[i].r)
{
t[i].p = 0;
t[i].q = 1;
continue;
}
while(l>t[i].l) add(a[--l]);//注意顺序 先增加分子,再cnt++
while(r<t[i].r) add(a[++r]);
while(l<t[i].l) del(a[l++]);
while(r>t[i].r) del(a[r--]);
t[i].p = ans-(r-l+1);
t[i].q = (ll)(r-l+1)*(r-l);
ll d = gcd(t[i].p,t[i].q);
t[i].p /= d;
t[i].q /= d;
}
sort(t+1,t+1+m,cmp2);
for(int i=1;i<=m;i++)
printf("%lld/%lld\n",t[i].p,t[i].q);
return 0;
}