题意:
给出一个长度为n的序列,每次询问一个区间[l,r];
查询在这个区间中取出两个数恰好相等的概率;
每个数大小在[0,n]内,概率用既约分数表示;
题解:
考虑一个区间的答案,显然是合法方案数/取数的所有可能;
也就是 ∑C[同种数字个数][2]/C[r-l+1][2];
但是这个东西对一次询问的处理复杂度是O(r-l+1)的;
那么考虑上莫队算法,处理这样的区间问题;
很容易发现每次修改边界可以做到O(1)完成;
void update(int x,int op) { now-=C[s[a[x]]][2]; s[a[x]]+=op; now+=C[s[a[x]]][2]; }
C[i][j]是组合数,s[x]是当前区间x的数量,now是当前答案;
然后将询问排序处理,第一关键字左端点所在块,第二关键字右端点;
当块的大小为√n时,可以证明复杂度不会超过O(n√n);
然后每个区间转移到下一个区间就是有复杂度保证的暴力咯;
码量似乎不算太巨大,思想也比较简单,很神的暴力算法;
代码:
#include<math.h> #include<stdio.h> #include<string.h> #include<algorithm> #define N 51000 using namespace std; struct query { int pos,l,r,no; }Q[N]; int a[N],s[N],ans_up[N],ans_do[N],C[N][3],now; bool cmp(query a,query b) { if(a.pos==b.pos) return a.r<b.r; return a.pos<b.pos; } int gcd(int a,int b) { if(!a||!b) return a?a:b; int t=a%b; while(t) { a=b,b=t; t=a%b; } return b; } void update(int x,int op) { now-=C[s[a[x]]][2]; s[a[x]]+=op; now+=C[s[a[x]]][2]; } int main() { int n,m,k,i,j,l,r; scanf("%d%d",&n,&m); int bk=sqrt(n); for(i=1;i<=n;i++) scanf("%d",a+i); for(i=0;i<=n;i++) { C[i][0]=1; for(j=1;j<=2;j++) C[i][j]=C[i-1][j]+C[i-1][j-1]; } for(i=1;i<=m;i++) { scanf("%d%d",&Q[i].l,&Q[i].r); Q[i].no=i,Q[i].pos=Q[i].l/bk; } sort(Q+1,Q+m+1,cmp); l=0,r=0,now=0,s[50001]=1,a[0]=50001; for(i=1;i<=m;i++) { while(l<Q[i].l) update(l++,-1); while(l>Q[i].l) update(--l, 1); while(r<Q[i].r) update(++r, 1); while(r>Q[i].r) update(r--,-1); ans_up[Q[i].no]=now; ans_do[Q[i].no]=C[r-l+1][2]; } for(i=1;i<=m;i++) { k=gcd(ans_up[i],ans_do[i]); printf("%d/%d\n",ans_up[i]/k,ans_do[i]/k); } return 0; }
时间: 2024-10-05 02:02:07