【Ynoi 2019 模擬賽】Yuno loves sqrt technology III

ProtectEMmm發表於2024-09-07

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;
}