题解 P1494 【[国家集训队]小Z的袜子】
题目分析
首先我们看一个简化的问题
给定一个序列,以及多组询问,针对每组询问,回答该区间有多少方案选出两个值相同的元素(不能调换左右)。
在上面的问题中,假设有n个值相同的元素,那方案数肯定是n*(n-1)/2,所以每个元素对答案贡献为在它之前的元素个数;
那我们回到之前的问题
题目中说可以调换左右,但因为所有的的袜子可以调换左右,所以就等价与上面的问题,我们只要把所有的选出的相同袜子的方案数除以所有的方案数。
附代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,t;
int a[500005];
struct node
{
int l,r;
int pos;
}e[500005];
inline bool cmp(node a,node b)
{
return (a.r/t)==(b.r/t)?a.l<b.l:a.r<b.r;//排序;
}
long long ans[500005],cnt[500005];//不开long long一场空;
long long sum[500005];
long long k=0;
inline void sk(int x)
{
k+=cnt[a[x]];
cnt[a[x]]++;
}
inline void hk(int x)
{
cnt[a[x]]--;
if(cnt[a[x]]!=0) k-=cnt[a[x]];
}
int f[500005];
int main()
{
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;i++) scanf("%d",&a[i]);
t=n/sqrt(m*2/3);
for(register int i=1;i<=m;i++)
{
scanf("%d%d",&e[i].l,&e[i].r);
e[i].pos=i;
}
sort(e+1,e+m+1,cmp);
int l=1,r=0;
for(register int i=1;i<=m;i++)
{
while(l<e[i].l) hk(l++);
while(l>e[i].l) sk(--l);
while(r<e[i].r) sk(++r);
while(r>e[i].r) hk(r--);
long long d=e[i].r-e[i].l+1;
if(e[i].l==e[i].r) f[e[i].pos]=1;
ans[e[i].pos]=k;
sum[e[i].pos]=d*(d-1)/2;
}
for(register int i=1;i<=m;i++)
{
long long d=__gcd(ans[i],sum[i]);
if(f[i]==1) printf("0/1\n");//特判,就因为这个我交了好几发.
else printf("%lld/%lld\n",ans[i]/d,sum[i]/d);
}
return 0;
}