1. 程式人生 > 其它 >LOJ#2874. 「JOISC 2014 Day1」歷史研究 回滾莫隊

LOJ#2874. 「JOISC 2014 Day1」歷史研究 回滾莫隊

題意

定義區間內某個數的重要度為該數數值乘以其在區間內的出現次數,\(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;
}