LOJ#2874. 「JOISC 2014 Day1」歷史研究 回滾莫隊
阿新 • • 發佈:2021-07-07
題意
定義區間內某個數的重要度為該數數值乘以其在區間內的出現次數,\(q\)次詢問,每次詢問需要回答區間內重要度最大的數的重要度是多少。
回滾莫隊解法
擴張區間時很容易維護答案,但收縮區間時不易維護,可以直接用只支援增加的回滾莫隊。
回滾莫隊按如下規則實現
按照普通莫隊的規則進行排序,分三種情況討論:
1.該次詢問的左右端點在同一塊中
直接暴力更新答案。計算出答案之後再把對cnt的修改改回去
2.該次詢問的左端點和上一次詢問在同一塊中
將當前詢問所在塊記為p,塊的左右端點記為L[p]和R[p]。
維護一個變量表示\([R[p]+1,r]\)這一段區間的答案。
將莫隊演算法左端點移動到[R[p]+1]處,對於每個詢問將區間拓展到詢問的左端點後回滾, 右端點是單調增的,無需回滾。
3.該次詢問的左端點和上一次詢問的左端點不在同一塊中。
將莫隊演算法的左端點和右端點分別移動至\(R[p]+1\)和\(R[p]\),繼續執行莫隊的正常流程。
程式碼如下
#include <bits/stdc++.h> using namespace std; #define ll long long const int maxn = 1e5 + 7; int n, q, blo, L[maxn], R[maxn], siz, tot, pos[maxn]; ll x[maxn], cnt[maxn], d[maxn], ans[maxn], cnt2[maxn], tmpans, tmpans2; int rd() { int s = 0, f = 1; char c = getchar(); while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); } while (c >= '0' && c <= '9') { s = s * 10 + c - '0'; c = getchar(); } return s * f; } struct query { int l, r, id; bool operator < (const query &rhs) const { return pos[l] == pos[rhs.l] ? r < rhs.r : l < rhs.l; } }que[maxn]; void add(int p, ll &ans) { cnt[x[p]]++; ans = max(ans, cnt[x[p]]*d[x[p]]); } void del(int p) { cnt[x[p]]--; } int main() { n = rd(); q = rd(); siz = sqrt(n); blo = n / siz; for (int i = 1; i <= n; i++) x[i] = d[i] = rd(); sort(d + 1, d + n + 1); for (int i = 1; i <= n; i++) { if (i == 1 || d[i] != d[i-1]) d[++tot] = d[i]; } for (int i = 1; i <= blo; i++) { L[i] = (i - 1) * siz + 1; R[i] = i * siz; } if (R[blo] < n) { ++blo; L[blo] = R[blo - 1] + 1; R[blo] = n; } for (int i = 1; i <= n; i++) x[i] = lower_bound(d + 1, d + tot + 1, x[i]) - d; for (int i = 1; i <= q; i++) { que[i].l = rd(); que[i].r = rd(); que[i].id = i; } for (int i = 1; i <= blo; i++) { for (int j = L[i]; j <= R[i]; j++) { pos[j] = i; } } sort (que + 1, que + q + 1); int lst = 0, l = 1, r = 0, tmpl; /* 只增不減的回滾莫隊 按照普通莫隊的方法排序,假設左端點所在塊序號為p 詢問將會按照如下順序出現 1.左端點與右端點都在p中。 直接暴力加點求答案,修改都是O1的,每一個這種詢問複雜度O(sqrt(n)) 掃一遍[que[i].l, que[i].r] 做完再把cnt改回去就行 注意這裡的cnt得和莫隊的cnt區分開 2.右端點不在p中,但左端點和上一次詢問的左端點都在p中 右端點可以直接從上一次詢問的右端點拓展過來 所有這一類詢問的時間之和是O(n)級別 左端點從R[p]+1拓展過來,求完答案後改回R[p+1] O(sqrt(n)) 注意答案的維護方式,應該開一個變數來維護l在同一塊內的答案 同時需要另外一個變數用來求出回滾之前得到的答案 3.左端點到了新的塊p中,那麼需要重新初始化一下,把莫隊的l設為R[p]+1, r設為R[p],要保證同一塊內右端點全都是單調增的。 * */ for (int i = 1; i <= q; i++) { if (pos[que[i].l] == pos[que[i].r]) { //l,r在同一塊內,直接暴力 ans[que[i].id] = 0; for (int j = que[i].l; j <= que[i].r; j++) { cnt2[x[j]]++; ans[que[i].id] = max(ans[que[i].id], cnt2[x[j]] * d[x[j]]); } for (int j = que[i].l; j <= que[i].r; j++) { cnt2[x[j]]--; } continue; } if (pos[que[i].l] != lst) { while (l < R[pos[que[i].l]]+1) { del(l); l++; } while (r > R[pos[que[i].l]]) { del(r); r--; } lst = pos[que[i].l]; tmpans = 0; } while (r < que[i].r) { ++r; add(r, tmpans); //直接更新上一次的ans } tmpl = l; tmpans2 = tmpans; while (tmpl > que[i].l) { --tmpl; add(tmpl, tmpans2); } //回滾 while (tmpl < l) { del(tmpl); ++tmpl; } ans[que[i].id] = tmpans2; } for (int i = 1; i <= q; i++) printf("%lld\n", ans[i]); return 0; }