bzoj2038 小Z的袜子【莫队算法模板+详解】

解题思路:

莫队出的模板题。

如果我们知道了询问区间中每种颜色的数量 cnti ,那么一种颜色的贡献就是 C2cnti ,总方案数是 C2rl+1 ,每种颜色贡献求和再与总方案数求gcd即可。

关键是如何快速统计区间内每种颜色的数量,这就要用到莫队算法。

考虑建立两个指针l,r,表示区间[l,r]内每种颜色的数量已知。
再将询问离线,按询问左端点所在块(块大小为 n )为第一关键字,右端点坐标为第二关键字排序,每次询问一位位暴力挪动l,r指针到该询问左右端点对应位置,同时修改贡献即可(挪一次指针只会改变一种颜色的数量,直接减去原来贡献,加上新贡献),这就是莫队算法,可以证明指针挪动次数为 O(nn) ,证明如下:

先考虑左指针。
当询问左端点在同一块时,每次挪动不超过 n 次;换块时,最多移动 2n 次,所以左指针移动次数是 O(nn) 的。

再考虑右指针。
当询问左端点在同一块时,询问右端点单调递增,一块内最多移动n次;当换块时,右指针最多从n挪回1,也是n次;而整个区间最多会被分为 n 块,所以右端点移动次数也是 O(nn) 的。

综上,莫队算法的时间复杂度是 O(nn) 的。

其实还有带修改的莫队,可以做做bzoj2120,
题解详见http://blog.csdn.net/cdsszjj/article/details/78397256

#include
using namespace std;

int read()
{
    int i=0,f=1;char c;
    for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=50005;
int n,m,S,a[N],cnt[N];
struct node
{
    int l,r,id,block;
    inline friend bool operator < (const node &a,const node &b)
    {return a.blockq[N];
pair<int,int>ans[N];

int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}

int calc(int x)
{
    return 1ll*x*(x-1)/2;
}

int main()
{
    //freopen("lx.in","r",stdin);
    n=read(),m=read(),S=sqrt(n);
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=1;i<=m;i++)
    {
        q[i].l=read(),q[i].r=read(),q[i].id=i;
        q[i].block=(q[i].l-1)/S+1;
    }
    sort(q+1,q+m+1);
    int l=1,r=0,num=0;
    for(int i=1;i<=m;i++)
    {
        int tot=calc(q[i].r-q[i].l+1);
        while(l<q[i].l)num=num-calc(cnt[a[l]])+calc(--cnt[a[l]]),++l;
        while(l>q[i].l)--l,num=num-calc(cnt[a[l]])+calc(++cnt[a[l]]);
        while(r<q[i].r)++r,num=num-calc(cnt[a[r]])+calc(++cnt[a[r]]);
        while(r>q[i].r)num=num-calc(cnt[a[r]])+calc(--cnt[a[r]]),--r;
        int d=gcd(num,tot);
        ans[q[i].id]=make_pair(num/d,tot/d);
    }
    for(int i=1;i<=m;i++)
        cout<'/'<'\n';
    return 0;
}

你可能感兴趣的:(莫队算法,bzoj)