離線

becaido

講師介紹

  • 建國中學 陳柏凱
  • OJ handle:becaido

因為想學習離線
所以來當離線講師

莫隊

Mo's Algorithm

詢問區間眾數

\(N,Q\leq 10^5\)

暴力做法

維護兩個指針 \(L,R\)

每次詢問把指針一格一格移到詢問的 \(l,r\)

每次加入 / 刪除元素可以
\(O(1)\) 維護目前眾數是多少

複雜度 \(O(NQ)\)

想減少指針移動的次數

每 \(B\) 個元素分一塊

對詢問的區間排序

離線處理

排序時先比較兩個詢問的 \(l\) 在哪一塊

如果不同塊可以小的排前面

如果一樣則比較 \(r\)

按照排序好的區間移動指針

複雜度分析

對於所有 \(l\) 在同一塊的詢問

左指針 \(L\) 每次詢問只會移動 \(O(B)\) 次

右指針 \(R\) 會遞增,加起來最多移動 \(O(N)\)

共 \(\frac{N}{B}\) 塊

複雜度 \(O(QB+\frac{N^2}{B})\)

\(B 取 \frac{N}{\sqrt{Q}}\) 最好

複雜度 \(O(N\sqrt{Q})\)

莫隊使用時機

可以很快速計算加入 / 刪除的元素

對整體的貢獻

區間詢問子區間 xor 最大值

\(N,Q\leq 10^5,a_i\leq 10^9\)

先轉成前綴 xor

\(p_i=a_1\oplus a_2\oplus\dots\oplus a_i\)

詢問 \(l,r\) 相當於求

\(l\leq i\leq j\leq r\)

\(p_j\oplus p_{i-1}\) 的最大值

先輩知識

01-Trie \(O(\log C)\) 求 xor 極值

想使用莫隊

但是刪除元素時不知道最大值會變什麼

改變莫隊的方法

一樣先排序好區間

對於在 \(l,r\) 在同一塊的詢問

可以 \(O(B\log C)\) 直接算

對於 \(l\) 都在同一塊的詢問

把每個區間分成左右兩邊

在 \(k\) 右邊是遞增的

每次先加入他們

在 \(k\) 左邊則是加完右邊後暴力加

每次回答完後要回溯操作

把在 \(k\) 左邊的人都刪掉

把最大值調回來

複雜度 \(O(QB\log C+\frac{N^2}{B}\log C)\)

\(B\) 一樣取 \(\frac{N}{\sqrt{Q}}\)

共 \(O(N\sqrt{Q}\log C)\)

此種技巧稱為回滾莫隊

練習題:ZJ i531

使用時機:

加入元素很好維護答案

刪除元素時很難維護答案

整體二分搜

有 \(n\) 個人,每個人的目標金額是 \(V_i\)

有 \(m\) 塊土地,第 \(i\) 塊土地的主人是 \(a_i\)

接下來會有 \(Q\) 次拍照,每次拍區間 \([L_i,R_i]\) 的土地

只要自己有任何一個土地被拍到就可以增加 \(C_i\) 的金錢

若一個人有多個土地只會加一次

問每個人會在第幾次拍照後達成目標

看到詢問最早會想到二分搜

但每個人都二分搜一次太慢了

試著讓全部人一起

來看 Code

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

const int N = 1e5 + 5;

int n, m, q;
int ans[N];
int a[N], V[N];
vector<int> p[N];
int L[N], R[N], C[N];

int last[N];
ll sum[N];

ll bit[N];
void upd(int pos, int x) {
    for (; pos <= m; pos += pos & -pos) bit[pos] += x;
}
ll que(int pos) {
    ll re = 0;
    for (; pos; pos -= pos & -pos) re += bit[pos];
    return re;
}
ll que(int l, int r) {
    return que(r) - que(l - 1);
}

void divide(int l, int r, vector<int> &ask) {
    if (l == r || ask.size() == 0) {
        for (int i : ask) ans[i] = l;
        return;
    }
    int mid = (l + r) / 2;
    vector<tuple<int, int, int>> op;
    vector<int> pos;
    for (int i = l; i <= mid; i++) {
        op.emplace_back(L[i], L[i], C[i]);
        op.emplace_back(R[i] + 1, L[i], -C[i]);
    }
    for (int i : ask) pos.insert(pos.end(), p[i].begin(), p[i].end());
    sort(op.begin(), op.end());
    sort(pos.begin(), pos.end());
    int cur = 0;
    for (int i : pos) {
        while (cur < op.size() && get<0>(op[cur]) <= i) {
            upd(get<1>(op[cur]), get<2>(op[cur]));
            cur++;
        }
        sum[a[i]] += que(last[a[i]] + 1, i);
        last[a[i]] = i;
    }
    vector<int> askl, askr;
    for (int i : ask) {
        if (sum[i] >= V[i]) askl.emplace_back(i);
        else {
            V[i] -= sum[i];
            askr.emplace_back(i);
        }
        last[i] = sum[i] = 0;
    }
    ask.clear();
    for (int i = 0; i < cur; i++) upd(get<1>(op[i]), -get<2>(op[i]));
    divide(l, mid, askl);
    divide(mid + 1, r, askr);
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m >> q;
    for (int i = 1; i <= m; i++) cin >> a[i], p[a[i]].emplace_back(i);
    for (int i = 1; i <= n; i++) cin >> V[i];
    for (int i = 1; i <= q; i++) cin >> L[i] >> R[i] >> C[i];
    vector<int> ask(n);
    iota(ask.begin(), ask.end(), 1);
    divide(1, q + 1, ask);
    for (int i = 1; i <= n; i++) {
        if (ans[i] == q + 1) ans[i] = -1;
        cout << ans[i] << '\n';
    }
}

練習題:TIOJ 1840

操作分塊

有一個長度 \(N\) 的陣列 \(a_1\sim a_N\)

有 \(Q\) 個操作

每個操作可能為單點改值或區間求和

可以用線段樹或 BIT

不過有可以不用資結的方法

暴力做法

建好前綴和

每次問區間和時先算出初始的答案

然後把之前全部改值都跑一遍

\(O(N+Q^2)\)

改成每 \(K\) 個詢問用這個做法

做完重新建前綴和

\(O(QK+\frac{NQ}{K})\)

\(K\) 取 \(\sqrt{N}\) 複雜度可以到 \(Q\sqrt{N}\)

有一棵樹,樹上有紅點跟藍點

一開始點 \(1\) 是紅點,其他點都是藍點

有 \(Q\) 個操作,把某個點設成紅點或是詢問一點到紅點的最短距離

可以用操作分塊的思想

每 \(K\) 個操作分一塊

可以先對目前的紅點跑一次多點源 bfs

算出每個點到紅點的最短距離

之後的詢問可以跟在他之前設成紅點的人用 lca 算距離

如果預處理 lca 可以 \(O(1)\) 求

複雜度 \(O(N\log N+Q\sqrt{N})\)

TIOJ 2202:King Game

有一個長度 \(N\) 的陣列 \(a_1\sim a_N\)

\(Q\) 個詢問,每次給 \(L,R,x,y\)

可以花 \(w_i\) 把 \(a_i\) 改成任何數字

想讓 \(a_L\sim a_R\) 裡有 \(z\) 種數字

且 \(x\leq z\leq y\)

問最小花費

\(N,Q,a_i,w_i\leq 10^5\)

離線

By becaido