链接
http://www.lydsy.com/JudgeOnline/problem.php?id=2038
题解
式子的推导 & 大暴力
把问题抽象一下,原序列有
N
个数,给出
这是一个古典概型,计算公式
化简得
观察式子发现,对于一次询问,分母只和 L,R 有关,这很简洁,可以只考虑分子。一个新加入的元素只会对分子的一项造成影响,形如, a(a−1) 变成 a(a+1) ,这两个式子只差了一个 2a ,所以给代表分子的变量直接加上 2a 就好了,同时 a++ 以进行下一次计算。考虑一个元素的删除,就是让 a(a−1) 变成 (a−1)(a−2) ,相当于直接给分子加 (−2a+2) ,同时 a−− 以进行下一次运算。
我直接做大暴力,对于每个询问直接扫描区间,复杂度 O(NM) ,结果 18s 过了.
莫队
这个算法很神奇,首先进行分块。
以
N−−√
为每块的大小,把整个序列分成
N−−√
块(关于为什么是
N−−√
,你可以设每块的大小为
size
,然后计算一下整个算法的时间复杂度,结果是
N(size+Nsize)
,再用均值不等式求最值,会发现当
size=N−−√
时整个算法的时间复杂度取到最小值
NN−−√
)
将询问进行排序,左端点所在的块的编号是第一关键字,右端点是第二关键字。然后暴力做即可,转移时使用上面推出的
O(1)
转移。
复杂度的分析:
因为询问已经排好序了,所以所有的询问可以看做大致的
N−−√
个部分,每一个部分内左端点的距离不大于
N−−√
。如果把块与块之间的转移单独拿出来(均摊
O(N)
),那么所有左端点之间转移的复杂度是
O(N+MN−−√)
。因为所有询问被分为
N−−√
块,每块内右端点是有序的,所以右端点转移的复杂度为
O(NN−−√)
。
综上,如果转移在
O(T)
的复杂度内完成,那么莫队算法的复杂度是
O{T[N+(N+M)N−−√]}
对于这道题目
T=1
,而且
M
和
代码
AC的暴力
//暴力
#include <cstdio>
#include <algorithm>
#include <cmath>
#define ll long long
#define maxn 51000
using namespace std;
ll N, M, col[maxn], cnt[maxn];
struct Quiry{ll l, r, ans1, ans2, id;}quiry[maxn];
bool operator<(Quiry q1, Quiry q2){return q1.l==q2.l?q1.r<q2.r:q1.l<q2.l;}
bool cmp(Quiry q1, Quiry q2){return q1.id<q2.id;}
void init()
{
ll i;
scanf("%lld%lld",&N,&M);
for(i=0;i<N;i++)scanf("%lld",col+i);
for(i=1;i<=M;i++)
scanf("%lld%lld",&quiry[i].l,&quiry[i].r),
quiry[i].l--,quiry[i].r--,quiry[i].id=i;
sort(quiry+1,quiry+M+1);
quiry[0].l=-1;
}
int gcd(ll a, ll b){return !b?a:gcd(b,a%b);}
void calc(ll a, ll b, ll num)
{
if(a==0){quiry[num].ans1=0,quiry[num].ans2=1;return;}
quiry[num].ans1=a/gcd(a,b), quiry[num].ans2=b/gcd(a,b);
}
void solve()
{
ll l=-1, r=-1, i, fz=0;
for(i=1;i<=M;i++)
{
for(;l<quiry[i].l;l++)if(l!=-1)fz+=-2*cnt[col[l]]--+2;
for(;r>quiry[i].r;r--)fz+=-2*cnt[col[r]]--+2;
for(r++;r<=quiry[i].r;r++)fz+=2*cnt[col[r]]++;r=quiry[i].r;
calc(fz,(quiry[i].r-quiry[i].l+1)*(quiry[i].r-quiry[i].l),i);
}
}
int main()
{
init();
solve();
sort(quiry+1,quiry+M+1,cmp);
for(ll i=1;i<=M;i++)printf("%lld/%lld\n",quiry[i].ans1,quiry[i].ans2);
return 0;
}
莫队
#include <cstdio>
#include <algorithm>
#include <cmath>
#define maxn 50010
#define ll long long
using namespace std;
ll col[maxn], lp[maxn], cnt[maxn], num[maxn], quiry[maxn][2], N, M, ans[maxn][2], size;
bool cmp(ll a, ll b)
{
ll la=quiry[a][0], lb=quiry[b][0], ra=quiry[a][1], rb=quiry[b][1];
return lp[la]==lp[lb]?ra<rb:lp[la]<lp[lb];
}
void input()
{
ll i, size;
scanf("%lld%lld",&N,&M);size=sqrt(N);
for(i=1;i<=N;i++)scanf("%lld",col+i),lp[i]=i/size;
for(i=1;i<=M;i++)num[i]=i,scanf("%lld%lld",quiry[i],quiry[i]+1);
sort(num+1,num+M+1,cmp);
}
ll gcd(ll a, ll b){return !b?a:gcd(b,a%b);}
void solve()
{
ll i, l=0, r=0, ql, qr, fz=0;
for(i=1;i<=M;i++)
{
ql=quiry[num[i]][0], qr=quiry[num[i]][1];
for(;l<ql;l++)if(l)fz+=-2*cnt[col[l]]--+2;
for(l--;l>=ql;l--)if(l)fz+=2*cnt[col[l]]++;l=ql;
for(;r>qr;r--)if(r)fz+=-2*cnt[col[r]]--+2;
for(r++;r<=qr;r++)if(r)fz+=2*cnt[col[r]]++;r=qr;
if(fz==0)ans[num[i]][0]=0,ans[num[i]][1]=1;
else
{
ll L=qr-ql+1, g=gcd(L*(L-1),fz);
ans[num[i]][0]=fz/g, ans[num[i]][1]=(L-1)*L/g;
}
}
for(i=1;i<=M;i++)printf("%lld/%lld\n",ans[i][0],ans[i][1]);
}
int main()
{
input();
solve();
return 0;
}