题目大意:给出n个元素的序列,m个区间,求
其中f(j)表示在区间内颜色j出现的次数。
n,m<=50000
如果对于每个区间暴力统计颜色个数然后计算,时间复杂度O(mnq),q为颜色种类数。
瞬间爆炸。考虑怎么优化
不难发现,如果已知[l,r]的答案,可以在O(1)的时间内求出[l,r+1]/[l+1,r]/[l-1,r]/[l,r-1]的答案并更新(颜色)信息,这就是(传说中的)莫队算法。
但是,即使这样每次转移的时间复杂度仍然高达O(nm),考虑离线处理。
把区间分块,按照第一关键字为左端点按照所在的块的编号、第二关键字为右端点大小排序,依次转移。
左端点:在 一个块中转移 和 块与块之间转移 的时间复杂度均为O(sqrt(n))
右端点:对于左端点都在一个块中的询问,排序后右端点从小到大,时间复杂度为O(n),共sqrt(n)个块,时间复杂度为O(n*sqrt(n)). 左端点跨越一个块的时候,右端点最坏情况从最后跑到最前面,时间复杂度O(n),共n个块,时间复杂度O(n*sqrt(n)).
总时间复杂度O(n*sqrt(n))
莫队算法可以解决能离线处理且不满足区间加法的区间询问
分块大法好。
#include <cstdio>
#include <cmath>
#include <algorithm>
#define N 50005
#define M 300
using namespace std;
typedef long long LL;
int n,m,p,L,R,ans,a[N],cnt[N],pos[N];
struct Segment{
int l,r,ord,ansx,ansy;
bool operator < (const Segment& rhs) const {return pos[l]<pos[rhs.l] || pos[l]==pos[rhs.l] && r<rhs.r; }
}b[N];
bool cmp(const Segment& x,const Segment& y){return x.ord<y.ord;}
int gcd(int x,int y){ return !y ? x : gcd(y,x%y); }
int main(){
scanf("%d%d",&n,&m);
p=floor(sqrt(n));
for(int i=1;i<=n;i++)
if(i%p) pos[i]=i/p;
else pos[i]=i/p-1;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=m;i++) scanf("%d%d",&b[i].l,&b[i].r) , b[i].ord=i;
sort(b+1,b+1+m);
L=R=1; cnt[a[1]]++;
for(int i=1;i<=m;i++){
//upgrade
if(L<b[i].l) for(;L<b[i].l;L++) ans -= --cnt[a[L]];
else for(L--;L>=b[i].l;L--) ans += cnt[a[L]]++;
if(R>b[i].r) for(;R>b[i].r;R--) ans -= --cnt[a[R]];
else for(R++;R<=b[i].r;R++) ans += cnt[a[R]]++;
L=b[i].l , R=b[i].r;
b[i].ansx=ans , b[i].ansy=(LL)(b[i].r-b[i].l)*(b[i].r-b[i].l+1)/2;
}
sort(b+1,b+1+m,cmp);
for(int i=1;i<=m;i++){
if(!b[i].ansx) printf("0/1\n");
else n=gcd(b[i].ansx,b[i].ansy) , printf("%d/%d\n",b[i].ansx/n,b[i].ansy/n);
}
return 0;
}