莫队算法,感觉与trajan有异曲同工之妙。
通过合理安排询问,来使用时减少,妙不可言。
题目:小Z的袜子
题意:
给出一个长度为n的序列。m次询问在li到ri随机选取两个能凑成一对的几率。
需转化成分数输出,若几率为0,则输出0/1。
样例输入:
6 4 1 2 3 3 3 2 2 6 1 3 3 5 1 6样例输出:
2/5 0/1 1/1 4/15
解法:
通过查找规律,不难发现对于每一个询问答案为 询问中每个元素的平方和-(询问区间元素数量)/(询问区间元素数量)*(询问区间覆盖长度)
于是可以将其询问通过排序集中在几个块中,减少暴力操作数量。
代码:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define read(x) scanf("%d",&x)
#define For(x,deep,top) for(x=deep;x<=top;x++)
using namespace std;
const int NM = 50020;
int s[NM],pos[NM];
int n,m,block;
ll rt[NM];
ll ans;
struct question
{
int l,r,v;
ll s,m;
} qs[NM];
inline ll gcd (ll a,ll b)
{
return b == 0 ? a : gcd(b,a%b) ;
}
ll mt(ll x)
{
return x*x;
}
inline bool cmp1(question a,question b) //以块为第一关键字,区间右端点为第二关键字排序
{
if (pos[a.l] == pos[b.l]) return a.r<b.r;
return a.l < b.l;
}
inline bool cmp2(question a,question b)
{
return a.v < b.v;
}
inline void update(int p,int add)
{
ans-=mt(rt[s[p]]);
rt[s[p]]+=add;
ans+=mt(rt[s[p]]);
}
int main()
{
int k,p;
register int i; //玄学加速
read(n),read(m);
block = int(sqrt(n));
For(i,1,n) read(s[i]);
For(i,1,n) pos[i]=(i-1)/block+1; //分块
For(i,1,m)
{
read(k),read(p);
qs[i].l = k;
qs[i].r = p;
qs[i].v = i;
}
sort(qs+1,qs+1+m,cmp1); //通过合理处理顺序减少暴力修改操作
for (register int j=1,l=1,r=0;j<=m;j++)
{
for (;r<qs[j].r;r++) update(r+1,1); //调整右端位置
for (;r>qs[j].r;r--) update(r,-1);
for (;l<qs[j].l;l++) update(l,-1); //调整左端位置
for (;l>qs[j].l;l--) update(l-1,1);
if (qs[j].l-qs[j].r==0)
{
qs[j].s=0;
qs[j].m=1;
continue;
}
qs[j].s=(ll)(ans-qs[j].r+qs[j].l-1);
qs[j].m=(ll)(qs[j].r-qs[j].l+1)*(qs[j].r-qs[j].l);
ll t = gcd(qs[j].s,qs[j].m);
qs[j].s/=t;qs[j].m/=t;
}
sort(qs+1,qs+1+m,cmp2); //转换原来顺序输出答案
For(i,1,m)
printf("%I64d/%I64d\n",qs[i].s,qs[i].m);
return 0;
}