學習筆記——不帶修序列莫隊 (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;
}