[分塊] [Luogu AT_joisc2014_c] 歷史研究

Peppa_Even_Pig發表於2024-05-03

題目描述

IOI 國曆史研究的第一人——JOI 教授,最近獲得了一份被認為是古代 IOI 國的住民寫下的日記。JOI 教授為了透過這份日記來研究古代 IOI 國的生活,開始著手調查日記中記載的事件。

日記中記錄了連續 \(N\) 天發生的事件,大約每天發生一件。

事件有種類之分。第 \(i\) 天發生的事件的種類用一個整數 \(X_i\)
表示,\(X_i\) 越大,事件的規模就越大。

JOI 教授決定用如下的方法分析這些日記:

  • 選擇日記中連續的幾天 \([L,R]\) 作為分析的時間段;

  • 定義事件 \(A\) 的重要度 \(W_A\)\(A\times T_A\),其中 \(T_A\) 為該事件在區間 \([L,R]\) 中出現的次數。

現在,您需要幫助教授求出所有事件中重要度最大的事件是哪個,並輸出其重要度

注意:教授有多組詢問。

輸入格式

第一行兩個空格分隔的整數 \(N\)\(Q\),表示日記一共記錄了 \(N\) 天,詢問有 \(Q\) 次。

接下來一行 \(N\) 個空格分隔的整數表示每天的事件種類。

接下來 \(Q\) 行,每行給出 \(L,R\) 表示一組詢問。

輸出格式

輸出共有 \(Q\) 行,每行一個整數,表示對應的詢問的答案。

資料範圍

對於 \(100\%\) 的資料,\(1\le Q,N\le 10^5\)\(1\le X\le 10^9\)\(1\le L\le R\le 10^5\)

樣例輸入 #1

5 5
9 8 7 8 9
1 2
3 4
4 4
1 4
2 4

樣例輸出 #1

9
8
8
16
16

樣例輸入 #2

8 4
9 9 19 9 9 15 9 19
1 4
4 6
3 5
5 8

樣例輸出 #2

27
18
19
19

樣例輸入 #3

12 15
15 9 3 15 9 3 3 8 16 9 3 17
2 7
2 5
2 2
1 12
4 12
3 6
11 12
1 7
2 6
3 5
3 10
7 10
1 4
4 8
4 8

樣例輸出 #3

18
18
9
30
18
15
17
30
18
15
18
16
30
15
15

題解

本體的實質是找一段區間內一個數出現的次數,可以考慮用分塊來做;

定義 \(sum[i][j]\) 表示前i個塊中j這個數出現的位置,時間複雜度 $ O(n \sqrt n) $;

定義 $ f[i][j] $ 表示第 $ [i, j] $ 個塊中的重要度最大值,時間複雜度 $ O(n \sqrt n) $;

這樣,當我們查詢時,如果 $ l $ 和 $ r $ 在同一個塊,直接用桶陣列暴力求解;

若不在同一個塊,那麼我們可以用 $ O(1) $ 的時間求出中間整塊的重要度最大值,剩下的零散塊暴力求解,最後使用sum陣列找出 $ l $ 到 $ r $ 的每個數出現個數並和中間整塊的重要度最大值作比較,不斷更新,最後找出答案;

做此題的關鍵在於如何用 $ O(1) $ 的時間求出中間整塊的重要度最大值,這裡採用的方法是預處理(一般的時間複雜度為 $ O(n \sqrt n) $);

題目中的資料範圍有時候也會給正解提供思路;

程式碼

#include <iostream>
#include <cstdio>
#include <map>
#include <cmath>
using namespace std;
long long a[1000005];
int cnt;
int b[1000005];
long long c[1000005];
int n, q;
int st[1000005], ed[1000005];
int belog[1000005];
int sq;
int sum[325][100005];
long long f[325][325];
long long t[1000005];
map<long long, int> mp;
long long ask(int l, int r) {
	long long ans = 0;
	if (belog[l] == belog[r]) { //直接暴力;
		for (int i = l; i <= r; i++) {
			t[b[i]]++;
		}
		for (int i = l; i <= r; i++) {
			long long o = t[b[i]] * c[b[i]];
			ans = max(ans, o);
		}
		for (int i = l; i <= r; i++) {
			t[b[i]] = 0; //使用桶陣列注意最後清零;
		}
	} else {
		ans = f[belog[l] + 1][belog[r] - 1]; //中間整塊的最大值;
		for (int i = l; i <= ed[belog[l]]; i++) {
			t[b[i]] = sum[belog[r] - 1][b[i]] - sum[belog[l]][b[i]]; //統計中間整塊中b[i]出現的個數;
		}
		for (int i = st[belog[r]]; i <= r; i++) {
			t[b[i]] = sum[belog[r] - 1][b[i]] - sum[belog[l]][b[i]];
		}
		for (int i = l; i <= ed[belog[l]]; i++) {
			t[b[i]]++; //統計零散塊中b[i]出現的個數;
		}
		for (int i = st[belog[r]]; i <= r; i++) {
			t[b[i]]++;
		}
		for (int i = l; i <= ed[belog[l]]; i++) {
			long long o = t[b[i]] * c[b[i]];
			ans = max(ans, o); //更新答案;
		}
		for (int i = st[belog[r]]; i <= r; i++) {
			long long o = t[b[i]] * c[b[i]];
			ans = max(ans, o);
		}
		for (int i = l; i <= ed[belog[l]]; i++) {
			t[b[i]] = 0; //使用桶陣列注意最後清零;
		}
		for (int i = st[belog[r]]; i <= r; i++) {
			t[b[i]] = 0; //使用桶陣列注意最後清零;
		}
	}
	return ans;
}
int main() {
	cin >> n >> q;
	for (int i = 1; i <= n; i++) { //需要離散化;
		cin >> a[i];
		if (mp[a[i]] == 0) {
			mp[a[i]] = ++cnt; //mp[a[i]]是a[i]離散化後對應的值;
		}
		b[i] = mp[a[i]]; //b陣列是a陣列離散化後對應的陣列;
		c[b[i]] = a[i]; //c[i] == j代表i這個離散化後的值對應的真實值為j;
	}
	sq = sqrt(n);
	for (int i = 1; i <= sq; i++) {
		st[i] = sq * (i - 1) + 1;
		ed[i] = sq * i;
	}
	ed[sq] = n;
	for (int i = 1; i <= sq; i++) {
		for (int j = st[i]; j <= ed[i]; j++) {
			belog[j] = i;
		}
	}
	for (int i = 1; i <= sq; i++) {
		for (int j = 1; j <= ed[i]; j++) {
			sum[i][b[j]]++;
		}
	}
	for (int i = 1; i <= sq; i++) {
		for (int j = 1; j <= sq; j++) {
			long long ma = f[i][j - 1];
			for (int k = st[j]; k <= ed[j]; k++) {
				t[b[k]] = sum[j][b[k]] - sum[i - 1][b[k]];
			}
			for (int k = st[j]; k <= ed[j]; k++) {
				if (ma < t[b[k]] * c[b[k]]) ma = t[b[k]] * c[b[k]];
			}
			f[i][j] = ma;
			for (int k = st[j]; k <= ed[j]; k++) {
				t[b[k]] = 0; //使用桶陣列注意最後清零;
			}
		}
	}
	int l, r;
	for (int i = 1; i <= q; i++) {
		cin >> l >> r;
		cout << ask(l, r) << endl;
	}
	return 0;
}

相關文章