P1494 [国家集训队] 小 Z 的袜子
P1494 [国家集训队] 小 Z 的袜子
题目描述
作为一个生活散漫的人,小 Z 每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小 Z 再也无法忍受这恼人的找袜子过程,于是他决定听天由命……
具体来说,小 Z 把这 \(N\) 只袜子从 \(1\) 到 \(N\) 编号,然后从编号 \(L\) 到 \(R\) 的袜子中随机选出两只来穿。尽管小 Z 并不在意两只袜子是不是完整的一双,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。
你的任务便是告诉小 Z,他有多大的概率抽到两只颜色相同的袜子。当然,小 Z 希望这个概率尽量高,所以他可能会询问多个 \((L,R)\) 以方便自己选择。
然而数据中有 \(L=R\) 的情况,请特判这种情况,输出0/1。
提示
\(100\%\) 的数据中,\(N,M \leq 50000\),\(1 \leq L \leq R \leq N\),\(C_i \leq N\)。
Solution :
十分简单的莫队,非常适合入门。
我们发现分母是很容易确定的,所以我们统计分子就好了。考虑用桶维护区间内的颜色出现次数 \(bac_{col}\),我们新加一个节点 \(x\) ,假设其颜色为 \(bac_{col}\) \(col_x\) 那么区间内的点对 \(x\) 产生的贡献就是 \(bac_{col_{x}}\)。
那么这个贡献显然是可以 \(O(1)\) 转移的,我们直接上莫队。
至于莫队算法嘛:
我似乎不能用比较精炼的语言概括其思想,但是它能应用的特征十分明显:询问离线,无修改操作,插入或删除一个点时,重新统计贡献的复杂度不高。
算法流程:
我们将所有询问排序,用两个指针 \(l,r\) 维护当前区间,在状态转移时移动指针。
Code:
#include<bits/stdc++.h>
#define int long long
const int N=5e4+5;
using namespace std;
int a[N],bac[N],blo[N],ans[N],len[N];
struct task{
int l,r,L,R,id;
bool operator<(const task &t)const{
return l==t.l ? (l&1 ? R<t.R : t.R<R) :l < t.l;
}
}q[N];
int n,m,S,sum;
inline void add(int x){sum+=bac[a[x]]++;}
inline void del(int x){sum-=--bac[a[x]];}
int gcd(int x,int y){return y ? gcd(y,x%y) : x;}
void work()
{
cin>>n>>m;
S=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
blo[i]=(i-1)/S+1;
}
for(int i=1,l,r;i<=m;i++)
{
scanf("%lld%lld",&l,&r);
q[i]={blo[l],blo[r],l,r,i};
len[i]=r-l+1;
}
sort(q+1,q+1+m);
int l=1,r=0;
for(int i=1;i<=m;i++)
{
while(r<q[i].R)add(++r);
while(l<q[i].L)del(l++);
while(q[i].L<l)add(--l);
while(q[i].R<r)del(r--);
ans[q[i].id]=sum<<1;
}
for(int i=1;i<=m;i++)
{
len[i]*=len[i]-1;
if(len[i])
{
int d=gcd(ans[i],len[i]);
printf("%lld/%lld\n",ans[i]/d,len[i]/d);
}
else
{
printf("0/1\n");
}
}
}
#undef int
int main()
{
//freopen("P1494_1.in","r",stdin);freopen("P1494.out","w",stdout);
work();
return 0;
}

浙公网安备 33010602011771号