BZOJ1878: [SDOI2009]HH的项链(莫队)

传送门

  • 题意

给你一个数列,询问(l,r)中不同种类的数的个数。

  • 题解

很经典的莫队算法,这道题可以说是板题了。

所谓莫队就是将询问排序,这次的询问部分信息与上次询问一致,来尽量压缩时间,对于一次询问(l1,r1),由上次询问(l2,r2),只需移动|l1-l2|+|r1-r2|步即可。

对于这道题,对序列分块进行适当分块,再对于询问(l,r)区间排序可以做到 O(nn) 的时间复杂度。(当然你发现其实询问是两点的曼哈顿距离,然后写个曼哈顿树会更优,不过复杂度上没什么显著的提高,且曼哈顿树实现较为困难,所以不再赘述)。

分块的方法:

不妨设块大小为S,块的个数为k(k*S=n)。
对于询问排序,先看左端点所在的区间,再看右端点的大小排序。

对于右端点,在左端点在一个块内时,右端点单调向右移动,最多移动 n 步。左端点所在块是单调递增的,共有 k 个块,所以右端点会移动 nk 次。

对于左端点,如果区间不动每次询问会在区间内移动 S 次(对于跨过区间的步数,因为不会回到原来的块,所以大致一共会移动 n 步),又因为有 m 次询问,所以至多移动 Sm+nSm

设两种移动大致相等,由均值思想,当 nkSm 时间复杂度较为优秀,即是 S100 。(实测 S=100 耗时1376 ms, S=n200 耗时1732 ms)。

当然,其实块的大小大部分情况下还是取 n 较好(当然,这只是时间复杂度做到 O(nn) 时的情况,如果再加个 log 什么的还是打表找最小值吧)。

  • Code:
#include
#include
#include
using namespace std;
const int Maxn=5e4+50;
const int Maxm=2e5+50;
const int Maxval=1e6+50;

const int S=100;

inline int read()
{
    char ch=getchar();
    int i=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
    return i*f;
}

int buf[50];
inline void W(int x)
{
    if(!x)putchar('0');
    if(x<0)putchar('-'),x=-x;
    while(x)buf[++buf[0]]=x%10,x/=10;
    while(buf[0])putchar('0'+buf[buf[0]--]);
}

int n,m,a[Maxn],cnt[Maxval],ans[Maxm];

struct node
{
    int l,r,id;
    friend inline bool operator < (const node &a,const node &b)
    {
        int s1=(a.l-1)/S+1,s2=(b.l-1)/S+1;
        if(s1!=s2)return s1return a.rint main()
{
    n=read();
    for(int i=1;i<=n;i++)a[i]=read();
    m=read();
    for(int i=1;i<=m;i++)q[i].l=read(),q[i].r=read(),q[i].id=i;
    sort(q+1,q+m+1);
    int L=0,R=0;
    for(int i=1;i<=m;i++)
    {
        int l=q[i].l,r=q[i].r;
        while(lif(!cnt[a[--L]]++)ans[q[i].id]++;
        while(l>L)if(!--cnt[a[L++]])ans[q[i].id]--;
        while(rif(!--cnt[a[R--]])ans[q[i].id]--;
        while(r>R)if(!cnt[a[++R]]++)ans[q[i].id]++;
        ans[q[i].id]+=ans[q[i-1].id];
    }
    for(int i=1;i<=m;i++)W(ans[i]),putchar('\n');
}

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