神奇的莫队算法,用来解决可离线无修改的区间查询问题:
- 首先对原序列进行分块,√n块每块√n个;
- 然后对所有查询的区间[l,r]进行排序,首先按l所在的块序号升序排序,如果一样就按r升序排序;
- 最后就按顺序一个一个求出各个查询的结果:知道[l,r]的答案,并且在此基础上能在比较快地在O(x)得到相邻区间[l+1,r]、[l-1,r]、[l,r-1]、[l,r+1]的答案,那样就能从[l,r]的基础上对lr加加减减得到任意一个区间[l',r']的答案。
看似暴力,但这样做的时间复杂度是O(x*n*√n) !因为:
- l是按其所在块序号排列,同一块里面一次最多√n次++l或--l到达目标;一块最多大概√n次加加减减;总共√n块;所以l改变的次数顶多也就√n*√n*√n。
- r在同一块是升序的,所以同一块最多n次++r;下一块时r假设在上一块到达最远,那最多n次--r回到目标;总共√n块;所以r改变次数顶多也就(n+n)*√n。
- 而每次加加减减转移新答案的代价是x,所以时间复杂度是O(x*n*√n) !
这一题,设每个区间[l,r]各个颜色的袜子数分别为$a,b,c,d,\dots$,每个区间[l,r]的答案就是$(C_a^2+C_b^2+C_c^2+C_d^2+\cdots)/C_{r-l+1}^2$,展开化简得:
$$(a^2+b^2+c^2+d^2+\cdots-a-b-c-d-\cdots)/((r-l+1)*(r-l+1-1))$$
$$(a^2+b^2+c^2+d^2+\cdots-(r-l+1))/((r-l+1)*(r-l))$$
其中$(a^2+b^2+c^2+d^2+\cdots)$便可作为莫队算法处理的区间答案,开个数组记录abcd...的个数可以在O(1)转移到相邻区间。
另外特判区间l=r的情况。。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define MAXN 55555 int block;
struct Query{
int i,l,r;
bool operator<(const Query &q)const{
if(l/block==q.l/block) return r<q.r;
return l/block<q.l/block;
}
}query[MAXN]; long long gcd(long long a,long long b){
if(b==) return a;
return gcd(b,a%b);
} int seq[MAXN];
long long cnt[MAXN],ansx[MAXN],ansy[MAXN];
void insert(long long &res,int a){
res-=cnt[a]*cnt[a];
++cnt[a];
res+=cnt[a]*cnt[a];
}
void remove(long long &res,int a){
res-=cnt[a]*cnt[a];
--cnt[a];
res+=cnt[a]*cnt[a];
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
block=sqrt(n);
for(int i=; i<=n; ++i) scanf("%d",seq+i);
for(int i=; i<m; ++i){
query[i].i=i;
scanf("%d%d",&query[i].l,&query[i].r);
}
sort(query,query+m);
int l=,r=;
++cnt[seq[]];
long long res=;
for(int i=; i<m; ++i){
if(query[i].l==query[i].r){
ansx[query[i].i]=; ansy[query[i].i]=;
continue;
}
while(l<query[i].l){
remove(res,seq[l]);
++l;
}
while(l>query[i].l){
--l;
insert(res,seq[l]);
}
while(r<query[i].r){
++r;
insert(res,seq[r]);
}
while(r>query[i].r){
remove(res,seq[r]);
--r;
}
long long a=res-(query[i].r-query[i].l+),b=(query[i].r-query[i].l+1LL)*(query[i].r-query[i].l),c=gcd(b,a);
ansx[query[i].i]=a/c; ansy[query[i].i]=b/c;
}
for(int i=; i<m; ++i) printf("%lld/%lld\n",ansx[i],ansy[i]);
return ;
}