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

链接

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

题解

式子的推导 & 大暴力

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

ans=
,对于一段确定的区间,分母肯定是 (RL+1)(RL)2 ,考虑分子,如果有2个1,3个3,那么情况就是从2个1中选出2个1,或者从3个三种选出2个3,即 C22+C23 ,那就是说如果有多种数字出现次数分别为 a,b,c,d... ,那么这个问题的答案就是
ans=C2a+C2b+C2c+...(RL+1)(RL)2

化简得
ans=a(a1)+b(b1)+c(c1)+...(RL+1)(RL)

  观察式子发现,对于一次询问,分母只和 L,R 有关,这很简洁,可以只考虑分子。一个新加入的元素只会对分子的一项造成影响,形如, a(a1) 变成 a(a+1) ,这两个式子只差了一个 2a ,所以给代表分子的变量直接加上 2a 就好了,同时 a++ 以进行下一次计算。考虑一个元素的删除,就是让 a(a1) 变成 (a1)(a2) ,相当于直接给分子加 (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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值