bzoj2038: [2009国家集训队]小Z的袜子(hose) 莫队算法(分块)

70 篇文章 0 订阅
27 篇文章 0 订阅

莫队算法

能使用莫队算法的前提是这样的
如果我们已知[l,r]的答案,能在O(1)时间得到[l+1,r]的答案以及[l,r-1]的答案,即可使用莫队算法。时间复杂度为O(n^1.5)。如果那个只能在logn的时间求,则时间复杂度是O(n^1.5*log n)。
说白了,就是用一个“神奇的数据结构”维护插入、删除操作

这道题的话我们很容易用“数组”来实现那个“神奇的数据结构”,做到O(1)的从[l,r]转移到[l,r+1]与[l+1,r]

那么莫队算法怎么做呢?以下都是在转移为O(1)的基础下讨论的时间复杂度。另外由于n与m同阶,为了书写方便,我就全部写成n了……
如果已知[l,r]的答案,要求[l',r']的答案,我们很容易在O( | l - l' + | r - r' | )的时间复杂度内求得

莫涛大神是这么说的:把询问[l,r]抽象成一个点(l,r),题目就转化为求n个点的最小曼哈顿哈密尔顿路;由于这是个NP问题,所以我们希望找到一个稍微劣一点又不是很劣的但是能快速求得方案。莫涛大神在他的论文里使用了二维曼哈顿距离最小生成树
二维曼哈顿距离最小生成树可以用区域划分法+树状数组/线段树维护区间极值在nlogn的时间复杂度内完成构图,并用Kruskal在n log n时间内求得,但是代码比较繁琐。
这里介绍一个优美的替代品——分块
将n个数分成sqrt(n)块
按区间排序,以左端点所在块内为第一关键字,右端点为第二关键字,进行排序
也就是以( pos [l],r )排序
然后搞就可以了

为什么呢?
搞得过程是这样的:
一、i与i+1在同一块内,r单调递增,所以r是O(n)的。由于有n^0.5块,所以这一部分时间复杂度是n^1.5。
二、i与i+1跨越一块,r最多变化n,由于有n^0.5块,所以这一部分时间复杂度是n^1.5
三、i与i+1在同一块内时变化不超过n^0.5,跨越一块也不会超过2*n^0.5,不妨看作是n^0.5。由于有n个数,所以时间复杂度是n^1.5
于是就变成了O(n^1.5)了

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
#define maxn 60000
typedef long long sint;
int n,m;
int save[maxn],pos[maxn];
sint ans,s[maxn];
struct node
{
    int l,r,id;
    sint a,b;
}a[maxn];
sint gcd(sint a,sint b)
{
    if(!b) return a;
    return gcd(b,a%b);
}
sint sqr(sint x)
{
    return x*x;
}
bool cmp(node aa,node bb)
{
    if(pos[aa.l]==pos[bb.l]) return aa.r<bb.r;
    return aa.l<bb.l;
}
bool cmp2(node aa,node bb)
{
    return aa.id<bb.id;
}
void update(int p,int add)
{
	ans-=sqr(s[save[p]]);
	s[save[p]]+=add;
	ans+=sqr(s[save[p]]);
}
void solve()
{
    for(int i=1,l=1,r=0;i<=m;i++)
    {
        for(;r<a[i].r;r++)
            update(r+1,1);
        for(;r>a[i].r;r--)
            update(r,-1);
        for(;l<a[i].l;l++)
		    update(l,-1);
		for(;l>a[i].l;l--)
		    update(l-1,1);
		if(a[i].l==a[i].r)
		{
			a[i].a=0;a[i].b=1;
			continue;
		}
		a[i].a=ans-(a[i].r-a[i].l+1);
		a[i].b=(sint)(a[i].r-a[i].l+1)*(a[i].r-a[i].l);
		sint k=gcd(a[i].a,a[i].b);
		a[i].a/=k;a[i].b/=k;
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&save[i]);
    int len=sqrt(n);
    for(int i=1;i<=n;i++) pos[i]=(i-1)/len+1;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a[i].l,&a[i].r);
        a[i].id=i;
    }
    sort(a+1,a+1+m,cmp);
    solve();
    sort(a+1,a+1+m,cmp2);
    for(int i=1;i<=m;i++)
    {
        printf("%lld/%lld\n",a[i].a,a[i].b);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值