LOJ6285. 數列分塊入門 9 題解
阿新 • • 發佈:2021-11-09
設計操作:
- 區間眾數。
解題思路:
我攤牌了,我看的這篇題解:https://www.cnblogs.com/acfunction/p/10051345.html
寫的太好了!!
主要操作:
- \(p_{i,j}\):第 \(i\) 塊到第 \(j\) 塊的(最小的)眾數;
- \(s_{i,j}\):類似字首和,在前 \(i\) 個塊中 \(j\)(離散化的值)出現了幾次。
如何預處理:
- 對於 \(s\):直接每個塊掃一遍,複雜度 \(O(n \sqrt n)\);
- 對於 \(p\):雙重迴圈列舉 \(i,j\),開一個數組暴力統計每個數出現了多少次。複雜度 \(O(n \sqrt n)\)
88分程式(有一部分超時了):
#include <bits/stdc++.h> using namespace std; const int maxn = 100010; int n, blo, bl[maxn], m, // m表示不同的數值個數 s[505][maxn], // s[i][j]: 前i個塊中數字j出現了幾次 p[505][505], // p[i][j]: 第 [i,j] 塊中最小的眾數 a[maxn]; map<int, int> mp; vector<int> vec; inline int getid(int val) { return lower_bound(vec.begin(), vec.end(), val) - vec.begin() + 1; } void _test(int a, int b) { cout << "[^] " << a << " : " << b << endl; } int query(int l, int r) { mp.clear(); if (bl[l]+1 >= bl[r]) { int res, mx = 0; for (int i = l; i <= r; i ++) { int id = getid(a[i]); int t = ++ mp[id]; if (t > mx || t == mx && id < res) { mx = t; res = id; } } return vec[res-1]; } else { // 至少2個分塊 for (int i = l; i <= bl[l]*blo; i ++) mp[getid(a[i])] ++; for (int i = (bl[r]-1)*blo+1; i <= r; i ++) mp[getid(a[i])] ++; int res = p[bl[l]+1][bl[r]-1], mx = mp[res] + s[bl[r]-1][res] - s[bl[l]][res]; for (auto pi : mp) { int x = pi.first, y = pi.second; int tmp = y + s[bl[r]-1][x] - s[bl[l]][x]; if (tmp > mx || tmp == mx && x < res) { res = x; mx = tmp; } } return vec[res-1]; } } int main() { ios::sync_with_stdio(0); cin.tie(0); cin >> n; blo = sqrt(n); for (int i = 1; i <= n; i ++) { cin >> a[i]; bl[i] = (i - 1) / blo + 1; vec.push_back(a[i]); } sort(vec.begin(), vec.end()); vec.erase(unique(vec.begin(), vec.end()), vec.end()); m = vec.size(); // 求 p[i][j] : 第 i 個塊到第 j 個塊中的最小的眾數 for (int i = 1; i <= bl[n]; i ++) { mp.clear(); int res, mx = 0; for (int j = i; j <= bl[n]; j ++) { for (int k = (j-1)*blo+1; k <= min(j*blo, n); k ++) { int val = getid(a[k]); mp[val] ++; if (mp[val] > mx || mp[val] == mx && val < res) { res = val; mx = mp[val]; } } p[i][j] = res; } } // 求 s[i][j] : 前 i 個塊中數字 j(離散化後的值)出現的次數 mp.clear(); for (int i = 1; i <= bl[n]; i ++) { for (int j = (i-1)*blo+1; j <= min(i*blo,n); j ++) { mp[getid(a[j])] ++; } for (int j = 1; j <= m; j ++) s[i][j] = mp[j]; } for (int i = 0; i < n; i ++) { int l, r; cin >> l >> r; cout << query(l, r) << endl; } return 0; }
然後看了一下原作者的解法,原作者只開了上述的 \(p\) 陣列,沒有開 \(s\) 陣列,而是使用了一個 vector 陣列 \(ve[i]\) 儲存數值 \(i\) 對應的座標,而後通過二分找區間範圍內有多少 \(i\)。
注:這題分塊大小為 \(\sqrt n\) 會超時,將分塊大小調整為 \(200\) 可過本題。
100分程式:
#include <bits/stdc++.h> using namespace std; const int maxn = 100010; int n, blo, id, a[maxn], bl[maxn], p[505][505]; map<int, int> mp; int val[maxn], cnt[maxn]; vector<int> g[maxn]; void pre(int x) { memset(cnt, 0, sizeof(cnt)); int res = 0, mx = 0; for (int i = (x-1)*blo+1; i <= n; i ++) { cnt[a[i]] ++; int t = bl[i]; if (cnt[a[i]] > mx || cnt[a[i]] == mx && val[a[i]] < val[res]) { res = a[i]; mx = cnt[a[i]]; } p[x][t] = res; } } int mycount(int l, int r, int x) { return upper_bound(g[x].begin(), g[x].end(), r) - lower_bound(g[x].begin(), g[x].end(), l); } int query(int l, int r) { int res = p[bl[l]+1][bl[r]-1], mx = mycount(l, r, res); for (int i = l; i <= min(bl[l]*blo, r); i ++) { int t = mycount(l, r, a[i]); if (t > mx || t == mx && val[a[i]] < val[res]) { res = a[i]; mx = t; } } if (bl[l] != bl[r]) { for (int i = (bl[r]-1)*blo+1; i <= r; i ++) { int t = mycount(l, r, a[i]); if (t > mx || t == mx && val[a[i]] < val[res]) { res = a[i]; mx = t; } } } return res; } int main() { ios::sync_with_stdio(0); cin >> n; blo = 200; for (int i = 1; i <= n; i ++) { cin >> a[i]; bl[i] = (i - 1) / blo + 1; if (!mp[a[i]]) { mp[a[i]] = ++id; val[id] = a[i]; } a[i] = mp[a[i]]; g[a[i]].push_back(i); } for (int i = 1; i <= bl[n]; i ++) pre(i); for (int i = 1; i <= n; i ++) { int l, r; cin >> l >> r; if (l > r) swap(l, r); cout << val[query(l, r)] << endl; } return 0; }