这是个很奇怪的东西,不过有n*sqrt(n)的时间复杂度保证, 所以直接乱搞
一个区间[l,r]内抽到同种颜色袜子的概率(设num[i]表示该区间中颜色为i的袜子数量)
sum( num[i])*(num[i]-1) ) / (r-l+1)*(r-l), (1<=i<=n)
分母很容易求出来,我们单独考虑分子,上式化简为
(sum(num[i]*num[i]) - sum(num[i])) / (r-l+1)*(r-l)
而sum([i])就是这个区间的长度, 所以我们只要先统计平方和,最后减去区间长度即可
对于已知答案的区间[l,r],可以O(1)时间算出[l-1,r],[l,r+1],[l+1,r],[l,r-1]的答案(由于区间只改动了1,只有一种颜色数量改变,只要减去原来数量的平方,加上新的数量的平方)
离线处理询问:先将n分成sqrt(n)块,然后先按每个询问的l所在块的编号从小到大排序,相同块按r排序(为什么这样,可能是因为相邻区间的l和r间就相差比较少,可以发挥上面性质的优点)
然后再YY一下得到如下代码:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; typedef long long ll; ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; } int n,m,sum,sqn,pc[50007],c[50007],num[50007]; ll ans; struct query{ int l,r,id; ll a,b; inline void read(int i){scanf("%d%d",&l,&r);id=i;} bool operator < (const query& b)const{ return pc[l]<pc[b.l]||(pc[l]==pc[b.l]&&r<b.r)||(pc[l]==pc[b.l]&&r==b.r&&l<b.l); } }Q[50007]; bool cmp(const query& a,const query& b){return a.id<b.id;} int main() { scanf("%d%d",&n,&m);sqn=sqrt(n); for(int i=1,j=0;i<=n;i++){ scanf("%d",&c[i]); if(i%sqn==1) j++; pc[i]=j; } for(int i=1;i<=m;i++) Q[i].read(i); sort(Q+1,Q+m+1); int l=1,r=0; for(int i=1;i<=m;i++){ int p=Q[i].l,q=Q[i].r; while(r<q)r++,num[c[r]]++,ans+=num[c[r]]*num[c[r]]-(num[c[r]]-1)*(num[c[r]]-1); while(l<p)ans+=(num[c[l]]-1)*(num[c[l]]-1)-num[c[l]]*num[c[l]],num[c[l]]--,l++; while(r>q)ans+=(num[c[r]]-1)*(num[c[r]]-1)-num[c[r]]*num[c[r]],num[c[r]]--,r--; while(l>p)l--,num[c[l]]++,ans+=num[c[l]]*num[c[l]]-(num[c[l]]-1)*(num[c[l]]-1); Q[i].a=ans-(q-p+1);Q[i].b=(ll)(q-p+1)*(q-p); } sort(Q+1,Q+m+1,cmp); for(int i=1;i<=m;i++) { ll Gcd=gcd(Q[i].a,Q[i].b); if(!Q[i].a) printf("0/1 "); else printf("%lld/%lld ",Q[i].a/Gcd,Q[i].b/Gcd); } return 0; }