Luogu P5048 Yuno loves sqrt technology III
題意
給定一個長度為 \(n\) 的序列 \(a\)。
有 \(m\) 次詢問:查詢區間 \([l,r]\) 中眾數的出現次數。
強制線上。
資料範圍與約定
\(1 \le n,m,a_i \le 5*10^5\)。
題解
十年前《蒲公英》的做法,這道題只能拿 \(80\) 分,因為這道題卡了空間。下面來講一個現代的線上區間眾數做法。
看到區間查詢,首先還是想到線段樹。我們發現這題線段樹沒辦法 pushup。但是與《由乃打撲克》這題不同的是,這題不能 pushup 的原因是我們不會 merge 兩個區間的資訊,而不是因為層數。所以乍一看分塊也無能為力,想要莫隊卻被強制線上 ban 了。
如果可以像區間數顏色一樣,透過某種方式轉化成二維數點,或者類似於區間 mex 進行一些轉化,也許能做。但遺憾的是區間眾數起碼我所知道的,並不能進行轉化。
還是考慮分塊,不考慮區間資訊合併的情況下,看看該怎麼辦。分類討論一下:
-
查詢區間在一個塊內:開個桶計數,暴力掃一遍每個元素即可。特別強調不要用 memset 清空桶,複雜度不對。
-
查詢區間在多個塊內:這裡也分兩種情況,整塊和角塊:
-
整塊:既然不會資訊的 merge,那我們就不 merge 了。直接預處理出任意兩個塊之間的眾數。因為塊只有 \(O(\sqrt{n})\) 個,所以只會有 \(O(n)\) 個區間,如同《蒲公英》的預處理一樣。假設整塊合併後,眾數為 \(mode\),出現次數為 \(cnt\)。
-
角塊:如果我們已經有了中間整塊的眾數資訊了,那麼考慮到角塊最多有 \(2*\sqrt{n}\) 的數,考慮角塊上的數的影響。如果我們能獲取一個數在區間中出現的次數,就能更新 \(mode,cnt\) 了。但遺憾的是,這個做法就是已經過時的《蒲公英》的做法,需要開一個 \(n*\sqrt{n}\) 大小的字首和陣列,被卡空間了。
如果我們轉成判定問題呢?考慮到,\(cnt\) 最多加上 \(O(\sqrt{n})\) 次,總勢能是固定的。可以進行判定,一個數如果在區間 \([l,r]\) 中出現次數大於 \(cnt\),就可以給 \(cnt+1\),然後進行 while 迴圈。剩下的問題就是如何判定了。
我們可以開一個 vector 陣列,記錄每種數字出現的下標。然後我們再記錄一下每個數在對應的 vector 中的下標。對於左角塊的每個數,如果在 vector 中的下標,向右偏移 \(cnt\) 次後,下標小於等於 \(r\)。則說明在區間 \([l,r]\) 中至少出現了 \(cnt+1\) 次。右角塊的數同理。
-
最後我們就得到了時間複雜度 \(O(n\sqrt{n})\),空間複雜度 \(O(n)\) 的做法了。最後根據實測,塊長取 \(400\) 時跑得最快。
程式碼
#include<bits/stdc++.h>
using namespace std;
/*====================*/
#define endl "\n"
/*====================*/
typedef long long lnt;
/*====================*/
const int N = 5e5 + 10;
const int C = 5e5 + 10;
/*====================*/
const int S = 400;
const int B = N / S + 10;
/*====================*/
int n, m;
int col[N];
/*====================*/
int belong[N];
struct Block
{
int l, r;
Block(void)
{
l = r = 0;
}
}block[B];
int mode[B][B];
/*====================*/
int idxincolidx[N];
vector<int>colidx[C];
/*====================*/
int cnt[C];
/*====================*/
int Ask(int l, int r)
{
int res = 0;
if (belong[l] == belong[r])
{
for (int i = l; i <= r; ++i)
{
res = max(res, ++cnt[col[i]]);
}
for (int i = l; i <= r; ++i)
{
cnt[col[i]]--;
}
}
else
{
res = mode[belong[l] + 1][belong[r] - 1];
for (int i = l; i <= block[belong[l]].r; ++i)
{
while ((idxincolidx[i] + res < colidx[col[i]].size()) && (colidx[col[i]][idxincolidx[i] + res] <= r))
{
res++;
}
}
for (int i = block[belong[r]].l; i <= r; ++i)
{
while ((idxincolidx[i] - res >= 0) && (colidx[col[i]][idxincolidx[i] - res] >= l))
{
res++;
}
}
}
return res;
}
/*====================*/
void Solve(void)
{
do
{
cin >> n >> m;
for (int i = 1; i <= n; ++i)
{
cin >> col[i];
}
} while (false);//讀入
do
{
for (int i = 1; i <= n; ++i)
{
colidx[col[i]].push_back(i);
idxincolidx[i] = colidx[col[i]].size() - 1;
}
} while (false);//預處理各顏色下標
do
{
for (int i = 1; i <= n; ++i)
{
belong[i] = i / S + 1;
if (block[belong[i]].l == 0)
{
block[belong[i]].l = i;
}
block[belong[i]].r = i;
}
} while (false);//分塊預處理
do
{
for (int i = 1; i <= belong[n]; ++i)
{
for (int j = i; j <= belong[n]; ++j)
{
mode[i][j] = mode[i][j - 1];
for (int k = block[j].l; k <= block[j].r; ++k)
{
mode[i][j] = max(mode[i][j], ++cnt[col[k]]);
}
}
memset(cnt, 0, sizeof(cnt));
}
} while (false);//處理塊間眾數
do
{
int lastans = 0;
for (int i = 1; i <= m; ++i)
{
int l, r; cin >> l >> r;
l ^= lastans; r ^= lastans;
cout << (lastans = Ask(l, r)) << endl;
}
} while (false);//回答詢問
}
/*====================*/
int main()
{
#ifndef ONLINE_JUDGE
freopen("IN.txt", "r+", stdin);
#endif
ios::sync_with_stdio(false);
cin.tie(NULL), cout.tie(NULL);
int T = 1; //cin >> T;
while (T--)Solve();
return 0;
}