1. 程式人生 > >學習筆記——不帶修序列莫隊 (luogu2079)小B的詢問

學習筆記——不帶修序列莫隊 (luogu2079)小B的詢問

莫隊是一種對於詢問的離線演算法

時間複雜度:O(\(n \sqrt n\)

大致思想就是

首先將詢問離線,然後對原序列分塊,使得每一個\(l和r\)都在一個塊裡

然後按照左節點排序,若所在的塊相等,就比較右節點

int cmp1(Node a,Node b)
{
    if (pos[a.l]==pos[b.l]) return a.r<b.r;
    return a.l<b.l;
}

排序之後,我們再來分析一下時間複雜度;接下來我們會看到神奇的事情!!

剛才分析此方法的時候,我們是從L和R的偏移量分析的;我們仍然用這種方法來分析。

考慮一下在同一個塊的時候。由於L的範圍是確定的,所以每次L的偏移量是O(√N)

但是r的範圍沒有確定;r的偏移量是O(N)。

那麼從一個塊到另一個塊呢?

明顯地,r我們不需要作考慮,仍然是O(N)。

而L明顯最多也是2*√N,而且這種情況下,很快就會到下下一塊。所以也是O(√N)

由於有√N(根號N)個塊,所以r的總偏移量是O(N*√N)

而M個詢問,每個詢問都可以讓L偏移O(√N),所以L的總偏移量O(M*√N)

注意了,時間複雜度分析的時候一定要注意,r的偏移量和詢問數目是沒有直接關係的。

而L則恰恰相反;L的偏移量我們剛才也說明了,它和塊的個數沒有直接關係。

所以總的時間複雜度是:

O((N+M)*\(\sqrt n\)


在排序完之後,就按照順序,一個一個求解,跳l和r

下面介紹兩種操作 \(remove\)\(insert\),分別是將這個位置移除、將這個位置加入答案

QwQ我也不知道為什麼我一開始把這兩個合成了一個函式

inline void update(int pos,int add)
{
    ans-=poer(s[c[pos]]);
    s[c[pos]]+=add;
    ans+=poer(s[c[pos]]);
}

然後就是注意l和r 初始要設成 1和 0

void solve()
{
    int l=1,r=0;
    for (int i=1;i<=m;i++) 
    {
    
        while (r<a[i].r)
        {
            update(r+1,1);
            r++;
        }   
        while (r>a[i].r)
        {
            update(r,-1);
            r--;
        }
        while (l<a[i].l)
        {
             update(l,-1);
             l++;
        }
        while (l>a[i].l)
        {
            update(l-1,1);
            l--;
        }
        if (a[i].l==a[i].r)
        {
            a[i].ans=1;
            continue;
        }
        a[i].ans=ans;
        
    }
    return;
}

下面引入一個經典例題:

題目大意:
小B有一個序列,包含N個1~K之間的整數。他一共有M個詢問,每個詢問給定一個區間[L..R],求Sigma(c(i)^2)的值,其中i的值從1到K,其中c(i)表示數字i在[L..R]中的重複次數。

對於全部的資料,1<=N、M、K<=50000

那麼這道題就是一道經典的序列莫隊問題了

直接上程式碼了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath> 
#define ll long long

using namespace std;

const int maxn = 50010;

struct Node{
    int l,r,id;
    ll ans;
};

inline int read(){
    int f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return x*f;
}

Node a[maxn];
int pos[maxn];
int c[maxn],n,m,block;
long long s[maxn];
int k;
ll ans;

inline ll poer(ll x){
    return x*x;
}

int cmp1(Node a,Node b)
{
    if (pos[a.l]==pos[b.l]) return a.r<b.r;
    return a.l<b.l;
}

int cmp2(Node a,Node b)
{
    return a.id<b.id;
}

inline void update(int pos,int add)
{
    ans-=poer(s[c[pos]]);
    s[c[pos]]+=add;
    ans+=poer(s[c[pos]]);
}

void solve()
{
    int l=1,r=0;
    for (int i=1;i<=m;i++) 
    {
    
        while (r<a[i].r)
        {
            update(r+1,1);
            r++;
        }   
        while (r>a[i].r)
        {
            update(r,-1);
            r--;
        }
        while (l<a[i].l)
        {
             update(l,-1);
             l++;
        }
        while (l>a[i].l)
        {
            update(l-1,1);
            l--;
        }
        if (a[i].l==a[i].r)
        {
            a[i].ans=1;
            continue;
        }
        a[i].ans=ans;
        
    }
    return;
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for (int i=1;i<=n;i++)
      c[i]=read();
    block=(int)sqrt(n);
    for (int i=1;i<=n;i++)
    {
        pos[i]=(i-1)/block+1;
    }
    for (int i=1;i<=m;i++)
    {
        a[i].l=read();
        a[i].r=read();
        a[i].id=i;
    }
    ans=0;
    sort(a+1,a+1+m,cmp1);
    solve();
    sort(a+1,a+1+m,cmp2);
    for (int i=1;i<=m;i++)
    {
        printf("%lld\n",a[i].ans);
    }
    return 0;
}