您的位置:首页 > 其它

bzoj2038: [2009国家集训队]小Z的袜子(hose)

2017-01-24 00:12 375 查看

链接

http://www.lydsy.com/JudgeOnline/problem.php?id=2038

题解

式子的推导 & 大暴力

  把问题抽象一下,原序列有N个数,给出M次询问每次询问一段[L,R],问你在这段区间随便选两个数字,不相同的概率是多少。

  这是一个古典概型,计算公式ans=符合条件的数对数目总的数对数目,对于一段确定的区间,分母肯定是(R−L+1)(R−L)2,考虑分子,如果有2个1,3个3,那么情况就是从2个1中选出2个1,或者从3个三种选出2个3,即C22+C23,那就是说如果有多种数字出现次数分别为a,b,c,d...,那么这个问题的答案就是ans=C2a+C2b+C2c+...(R−L+1)(R−L)2

化简得

ans=a(a−1)+b(b−1)+c(c−1)+...(R−L+1)(R−L)

  观察式子发现,对于一次询问,分母只和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和N是同级别的所以时间复杂度为O(NN−−√)

代码

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: