HYSBZ - 2038 小Z的袜子(hose) 莫队 +公式推导

HYSBZ - 2038 小Z的袜子

莫队(分块版,不是二维曼哈顿距离什么什么最小生成树)就是分块排序优化暴力查找,减少查找区间之间的覆盖长度,从而优化时间复杂度,有一种说法很精彩

如果我们已知[l,r]的答案,能在O(1)时间得到[l+1,r]的答案以及[l,r-1]的答案,即可使用莫队算法。时间复杂度为O(n^1.5)。如果只能在logn的时间移动区间,则时间复杂度是O(n^1.5*logn)。 其实就是找一个数据结构支持插入、删除时维护当前答案。

这道题的话我们很容易用数组来实现,做到O(1)的从[l,r]转移到[l,r+1]与[l+1,r]。

那么莫队算法怎么做呢?以下都是在转移为O(1)的基础下讨论的时间复杂度。另外由于n与m同阶,就统一写n。 
如果已知[l,r]的答案,要求[l’,r’]的答案,我们很容易通过|l – l’|+|r – r’|次转移内求得。

对于它的时间复杂度分析

将n个数分成sqrt(n)块。 按区间排序,以左端点所在块内为第一关键字,右端点为第二关键字,进行排序,也就是以(pos [l],r)排序 
然后按这个排序直接暴力,复杂度分析是这样的: 
1、i与i+1在同一块内,r单调递增,所以r是O(n)的。由于有n^0.5块,所以这一部分时间复杂度是n^1.5。 
2、i与i+1跨越一块,r最多变化n,由于有n^0.5块,所以这一部分时间复杂度是n^1.5 
3、i与i+1在同一块内时变化不超过n^0.5,跨越一块也不会超过n^0.5,忽略*2。由于有n个数,所以时间复杂度是n^1.5 
于是就是O(n^1.5)了

这道题是比较模板的莫队分块了,对于一个区间询问[L,R],我们要求的ans是 
ans=(Σsum(color[i])−1)∗sum(color[i])/2)/((R−L+1)∗(R−L))ans=(Σsum(color[i])−1)∗sum(color[i])/2)/((R−L+1)∗(R−L)) 
简化可得 
ans=(Σ(sum(color[i])2)−(R−L+1))/((R−L+1)∗(R−L))ans=(Σ(sum(color[i])2)−(R−L+1))/((R−L+1)∗(R−L)) 
其中sum(color[i])sum(color[i])指第i种颜色在[L,R]中出现的次数 
那么我们现在求出各个询问区间中sum(color[i])2sum(color[i])2就行了,具体实现方法参照代码 
注意: 
1.当一种颜色数量ci增加1时,我们可以看出ans=ans−ci2+(ci+1)2ans=ans−ci2+(ci+1)2,简化可得ans=ans+(ci∗2+1)ans=ans+(ci∗2+1),同样减少1时,ans=ans−ci2+(ci−1)2ans=ans−ci2+(ci−1)2,简化得ans=ans+(ci∗2−1)ans=ans+(ci∗2−1),这样做的好处是减少乘法且可用位运算,优化常数(然而并没有什么卵用) 

文字转自:https://blog.csdn.net/xym_csdn/article/details/50889293

 

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=5e5+10;
struct node
{
    int l,r,id;
    ll p,q;// p 分子,q 分母
}t[maxn];
int n,m,a[maxn],num[maxn],cnt[maxn],block;
// a 原数组 , num[i] i在第几块(分块) ,cnt[i] 记录颜色数
ll ans;
bool cmp1(node a,node b)
{
    if(num[a.l]==num[b.l])
        return a.r<b.r;
    return a.l<b.l;
}
bool cmp2(node a,node b)
{
    return a.id<b.id;
}
ll gcd(ll a,ll b)
{
    if(!b)
        return a;
    return gcd(b,a%b);
}
void add(int x)   //把x位置的数字移进来
{
//    cnt[x]++;
//    if(cnt[x]==1)
//        ans++;
    ans += (cnt[x]<<1)+1;
    cnt[x]++;
}
void del(int x)  //把x位置的数字移出去
{
//    cnt[x]--;
//    if(cnt[x]==0)
//        ans--;
    ans += 1-(cnt[x]<<1);
    cnt[x]--;
}
int main()
{
    int l,r;
    scanf("%d%d",&n,&m);
    block = sqrt(n);
    for(int i=1;i<=n;i++)
        scanf("%d",a+i);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&t[i].l,&t[i].r);
        t[i].id = i;
        num[i] = (i-1)/block+1;
    }
    sort(t+1,t+m+1,cmp1);//按照区间排序
    l = 1 , r = 0;
    for(int i=1 ; i<=m ; i++)
    {
        if(t[i].l == t[i].r)
        {
            t[i].p = 0;
            t[i].q = 1;
            continue;
        }
        while(l>t[i].l)  add(a[--l]);//注意顺序  先增加分子,再cnt++
        while(r<t[i].r)  add(a[++r]);
        while(l<t[i].l)  del(a[l++]);
        while(r>t[i].r)  del(a[r--]);
        t[i].p = ans-(r-l+1);
        t[i].q = (ll)(r-l+1)*(r-l);
        ll d = gcd(t[i].p,t[i].q);
        t[i].p /= d;
        t[i].q /= d;
    }
    sort(t+1,t+1+m,cmp2);
    for(int i=1;i<=m;i++)
        printf("%lld/%lld\n",t[i].p,t[i].q);

    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值