Offline Algorithms

莫队算法

普通莫队

typedef struct _Query {
    int id, qLeftPt, qRightPt;
} Query;

Query qArr[SIZE];
int arr[SIZE], ans[SIZE], len, qNum, pos[SIZE];

int main() {
    /* Some code here... */
    // Partition
    int blockSize = sqrt(len);
    for (int i = 0; i < len; i++)
        pos[i] = i / blockSize;
    // Sort
    sort(qArr + 0, qArr + qNum, [](const Query & fst, const Query & snd) {
        if (pos[fst.qLeftPt] == pos[snd.qLeftPt])
            return fst.qRightPt < snd.qRightPt;
        return pos[fst.qLeftPt] < pos[snd.qLeftPt];
    });

    int cntLeftPt = 0, cntRightPt = 0, cntAns = 0;
    for (int i = 0; i < qNum; i++) {
        while (qArr[i].qLeftPt < cntLeftPt) {
            // Add left
            cntLeftPt--;
            int cntPt = cntLeftPt;
            // Process insertion of cntPt
        }
        while (qArr[i].qRightPt > cntRightPt) {
            // Add right
            cntRightPt++;
            int cntPt = cntRightPt;
            // Process insertion of cntPt
        }
        while (qArr[i].qLeftPt > cntLeftPt) {
            // Delete left
            int cntPt = cntLeftPt;
            // Process removal of cntPt
            cntLeftPt++;
        }
        while (qArr[i].qRightPt < cntRightPt) {
            // Delete right
            int cntPt = cntRightPt;
            // Process removal of cntPt
            cntRightPt--;
        }
        ans[qArr[i].id] = cntAns;
    }
    /* Some code here... */
}

CDQ 分治

$solve(l, r)$:$\forall k \in [l, r]$,若第 $k$ 项操作是查询,则计算 $[l, k - 1]$ 中修改对其造成的影响:

  • $mid = \frac{1}{2}(l + r)$;
  • $solve(l, mid)$;
  • $solve(mid + 1, r)$;
  • 计算第 $[l, mid]$ 项操作中所有修改对 $[mid + 1, r]$ 中所有查询造成的影响。

动态问题 $\rightarrow$ 静态问题。

三维偏序

void divideConquer(int headPt, int tailPt) {
    if (headPt == tailPt)
        return;
    int midPt = (headPt + tailPt) >> 1;
    divideConquer(headPt, midPt);
    divideConquer(midPt + 1, tailPt);

    int j = headPt;
    for (int i = midPt + 1; i <= tailPt; i++) {
        for (; j <= midPt && arr[j].b <= arr[i].b; j++)
            add(arr[j].c, arr[j].num);
        arr[i].ans += prefixSum(arr[i].c);
    }

    for (int i = headPt; i < j; i++)
        add(arr[i].c, -arr[i].num);

    inplace_merge(arr + headPt, arr + midPt + 1, arr + tailPt + 1, cmpSnd);
}

四维偏序

void divideConquer2(int headPt, int tailPt) {
    if (headPt == tailPt)
        return;
    int midPt = (headPt + tailPt) >> 1;
    divideConquer2(headPt, midPt);
    divideConquer2(midPt + 1, tailPt);
    int j = headPt;
    for (int i = midPt + 1; i <= tailPt; i++) {
        if (arr[i].type == LEFT)
            continue;
        for (; j <= midPt && arr[j].b < arr[i].b; j++)
            if (arr[j].type == LEFT)
                add(arr[j].c, 1);
        ans += prefixSum(arr[i].c);
    }
    for (int i = headPt; i < j; i++)
        if (arr[i].type == LEFT)
            add(arr[i].c, -1);
    inplace_merge(arr + headPt, arr + midPt + 1, arr + tailPt + 1, cmpSnd);
}

void divideConquer(int headPt, int tailPt) {
    if (headPt == tailPt)
        return;
    int midPt = (headPt + tailPt) >> 1;
    divideConquer(headPt, midPt);
    divideConquer(midPt + 1, tailPt);

    for (int i = headPt; i <= midPt; i++)
        arr[i].type = LEFT;
    for (int i = midPt + 1; i <= tailPt; i++)
        arr[i].type = RIGHT;

    inplace_merge(arr + headPt, arr + midPt + 1, arr + tailPt + 1, cmpFst);
    copy(arr + headPt, arr + tailPt + 1, tmp + headPt);
    divideConquer2(headPt, tailPt);
    copy(tmp + headPt, tmp + tailPt + 1, arr + headPt);
}

Luogu P1903:数颜色

class Query {
public:
    int type;   // 0: Query+, 1: Query-, 2: Modify
    int x, y, val;
    bool operator < (const _Query & snd) const {
        if (x != snd.x)
            return x < snd.x;
        if (y != snd.y)
            return y < snd.y;
        return type > snd.type;
    }
};

Query qArr[SIZE << 3]; 
set<int> st[VAL_SIZE];
int arr[SIZE], pre[SIZE], last[VAL_SIZE], bit[SIZE], ans[SIZE];

int lowbit(int n) { 
    return n & -n;
}

void add(int pos, int val) {
    pos += 3;
    for (int i = pos; i < SIZE; i += lowbit(i))
        bit[i] += val;
}

int prefixSum(int pos) {
    int ret = 0; pos += 3;
    for (int i = pos; i > 0; i -= lowbit(i))
        ret += bit[i];
    return ret;
}

void divideConquer(int headPt, int tailPt) {
    if (headPt == tailPt)
        return;
    int midPt = (headPt + tailPt) >> 1;
    divideConquer(headPt, midPt);
    divideConquer(midPt + 1, tailPt);

    int j = headPt;
    for (int i = midPt + 1; i <= tailPt; i++) {
        if (qArr[i].type == 2)
            continue;
        for (; j <= midPt && qArr[j].x <= qArr[i].x; j++)
            if (qArr[j].type == 2)
                add(qArr[j].y, qArr[j].val);
        ans[qArr[i].val] += (1 - (qArr[i].type << 1)) * prefixSum(qArr[i].y);
    }

    for (int i = headPt; i < j; i++)
        if (qArr[i].type == 2)
            add(qArr[i].y, -qArr[i].val);

    inplace_merge(qArr + headPt, qArr + midPt + 1, qArr + tailPt + 1);
}

int main() {
    memset(last, -1, sizeof(last));
    memset(bit, 0, sizeof(bit));
    int len, qNum, ansPt = 0, qPt = 0;
    cin >> len >> qNum;
    for (int i = 0; i < len; i++) {
        cin >> arr[i];
        pre[i] = last[arr[i]];
        qArr[qPt++] = {2, i, pre[i], 1};
        last[arr[i]] = i;
        st[arr[i]].insert(i);
    }

    for (int i = 0; i < qNum; i++) {
        char op; int x, y;
        cin >> op >> x >> y;
        x--;
        if (op == 'R') {  // Modify
            // Erase original
            qArr[qPt++] = {2, x, pre[x], -1};
            auto it = st[arr[x]].upper_bound(x);
            if (it != st[arr[x]].end()) {
                qArr[qPt++] = {2, *it, pre[*it], -1};
                pre[*it] = pre[x];
                qArr[qPt++] = {2, *it, pre[*it], 1};
            }
            st[arr[x]].erase(x);
            // Insert new
            it = st[y].upper_bound(x);
            if (it != st[y].end()) {
                pre[x] = pre[*it];
                qArr[qPt++] = {2, x, pre[x], 1};
                qArr[qPt++] = {2, *it, pre[*it], -1};
                pre[*it] = x;
                qArr[qPt++] = {2, *it, pre[*it], 1};
            } else {
                if (st[y].empty()) {
                    pre[x] = -1;
                    qArr[qPt++] = {2, x, pre[x], 1};
                } else {
                    it = prev(it);
                    pre[x] = *it;
                    qArr[qPt++] = {2, x, pre[x], 1}; 
                }
            }
            st[y].insert(x);
            arr[x] = y;

        } else {    // Query
            y--;
            qArr[qPt++] = {0, y, x - 1, ansPt};
            qArr[qPt++] = {1, x - 1, x - 1, ansPt};
            ansPt++;
        }
    }
    divideConquer(0, qPt - 1);
    for (int i = 0; i < ansPt; i++)
        cout << ans[i] << '\n';
    return 0;
}

Luogu P4169:天使玩偶

class Query {
public:
    int id, x, y;
    bool operator < (const struct _Query & snd) const {
        if (x != snd.x)
            return x < snd.x;
        if (y != snd.y)
            return y < snd.y;
        return id < snd.id;
    }
};
Query qArr[Q_SIZE], orig[Q_SIZE];
int bit[SIZE], ans[Q_SIZE];

int lowbit(int n) {
    return n & -n;
}

void addMax(int pos, int val) {
    pos++;
    for (int i = pos; i < SIZE; i += lowbit(i))
        bit[i] = max(bit[i], val);
}

void clear(int pos) {
    pos++;
    for (int i = pos; i < SIZE; i += lowbit(i))
        bit[i] = INT_MIN;
}

int prefixMax(int pos) {
    int ret = INT_MIN; pos++;
    for (int i = pos; i > 0; i -= lowbit(i))
        ret = max(ret, bit[i]);
    return ret;
}

void divideConquer(int headPt, int tailPt) {
    if (headPt == tailPt)
        return;
    int midPt = (headPt + tailPt) >> 1;
    divideConquer(headPt, midPt);
    divideConquer(midPt + 1, tailPt);

    int j = headPt;
    for (int i = midPt + 1; i <= tailPt; i++) {
        if (qArr[i].id == -1)
            continue;
        for (; j <= midPt && qArr[j].x <= qArr[i].x; j++)
            if (qArr[j].id == -1)
                addMax(qArr[j].y, qArr[j].x + qArr[j].y);
        int ret = prefixMax(qArr[i].y);
        if (ret != INT_MIN)
            ans[qArr[i].id] = min(ans[qArr[i].id], qArr[i].x + qArr[i].y - ret);
    }

    for (int i = headPt; i < j; i++)
        if (qArr[i].id == -1)
            clear(qArr[i].y);

    inplace_merge(qArr + headPt, qArr + midPt + 1, qArr + tailPt + 1);
}

int main() {
    int len, qNum; cin >> len >> qNum;
    for (int i = 0; i < len; i++) {
        cin >> qArr[i].x >> qArr[i].y;
        qArr[i].id = -1;
    }
    int qPt = len, idPt = 0;
    for (int i = 0; i < qNum; i++) {
        int op; cin >> op >> qArr[qPt].x >> qArr[qPt].y;
        qArr[qPt++].id = (op == 1 ? -1 : idPt++);
    }
    fill(bit + 0, bit + SIZE, INT_MIN);
    fill(ans + 0, ans + idPt, INT_MAX);
    copy(qArr + 0, qArr + qPt, orig + 0);
    // (x1 + y1) - (x2 + y2)
    divideConquer(0, qPt - 1);
    // (x1 - y1) + (x2 - y2)
    for (int i = 0; i < qPt; i++) {
       qArr[i] = orig[i];
       qArr[i].y = SIZE - 1 - qArr[i].y;
    }
    divideConquer(0, qPt - 1);
    // (-x1 + y1) + (-x2 + y2)
    for (int i = 0; i < qPt; i++) {
        qArr[i] = orig[i];
        qArr[i].x = SIZE - 1 - qArr[i].x;
    }
    divideConquer(0, qPt - 1);
    // (-x1 - y1) + (-x2 - y2)
    copy(orig + 0, orig + qPt, qArr + 0);
    for (int i = 0; i < qPt; i++) {
        qArr[i] = orig[i];
        qArr[i].x = SIZE - 1 - qArr[i].x;
        qArr[i].y = SIZE - 1 - qArr[i].y;
    }
    divideConquer(0, qPt - 1);
    for (int i = 0; i < idPt; i++)
        cout << ans[i] << '\n';
    return 0;
}

线段树分治

BZOJ 4025

给定一张 $n$ 个点的图,在 $T$ 时间内一些边会在 $s_i$ 时刻出现,并在 $t_i$ 时刻消失。询问每一个时刻该图是否是二分图。


$solve(l, r, Q)$:对于时刻 $[l, r]$,操作集为 $Q$,满足 $\forall q \in Q$,其存活时间区间与当前分治的时间区间交集非空;而这个函数的作用为对于 $[l, r]$ 内每个时刻,判断此时图是否是二分图。操作集 $Q$ 中元素 $q$ 带有 $4$ 个属性:$\langle u, v, l, r \rangle$。其中,$u, v$ 代表边的两个端点,而 $l, r$ 代表出现和消失时刻。我们可以考虑如下算法:

  • $mid = \frac{1}{2} (l + r)$,并新建两个空操作集 $Q_L, Q_R$;
  • 遍历 $q \in Q$:
    • 若 $q.l = l \land q.r = r$(说明这一操作对其子树中所有叶子节点都有影响,那么直接加边):
      • 并查集中合并 $q.u$ 和 $q.v$,并判断是否出现奇环;
      • 存在奇环?说明时间区间 $[l, r]$ 都凉了,均标记答案为否!撤销对并查集的操作并回溯(所以我们需要可撤销并查集,其实也很简单,就搞一个栈记录一下操作之前值的情况然后撤销的时候弹栈就好了);
    • 若 $q.l \le mid$:令 $q.r = \min⁡(q.r, mid)$,并把 $q$ 放入 $Q_L$;
    • 若 $q.r > mid$:$q.l = \max⁡(q.l, mid + 1)$,并把 $q$ 放入 $Q_R$;
  • 若 $l = r$,标记答案为是,撤销对并查集的操作并回溯
  • $solve(l, mid, Q_L)$;
  • $solve(mid + 1, r, Q_R)$;
  • 撤销对并查集的操作并回溯

class Query {
public:
    int from, to, leftPt, rightPt;
};

vector<Query> ques;
int ans[SIZE];
stack<pair<int *, int> > stk;
int parent[SIZE], siz[SIZE], dep[SIZE];

pair<int, int> getParent(int cntPt) {
    if (parent[cntPt] == cntPt)
        return make_pair(cntPt, 0);
    pair<int, int> ret = getParent(parent[cntPt]);
    ret.second ^= dep[cntPt];
    return ret;
}

bool merge(int fstPt, int sndPt) {
    pair<int, int> fst = getParent(fstPt), snd = getParent(sndPt);
    fstPt = fst.first, sndPt = snd.first;
    if (fstPt == sndPt)
        return false;
    if (siz[fstPt] < siz[sndPt])
        swap(fstPt, sndPt);

    stk.push(make_pair(parent + sndPt, parent[sndPt])); parent[sndPt] = fstPt;
    stk.push(make_pair(siz + fstPt, siz[fstPt])); siz[fstPt] += siz[sndPt];
    stk.push(make_pair(dep + sndPt, dep[sndPt])); dep[sndPt] = fst.second ^ snd.second ^ 1;
    return true;
}

void undo() {
    if (stk.empty())
        return;
    *stk.top().first = stk.top().second; stk.pop();
}

void divideConquer(int leftPt, int rightPt, const vector<Query> & vec) {
    int midPt = (leftPt + rightPt) >> 1, stkSiz = stk.size();
    vector<Query> leftVec, rightVec;
    for (int i = 0; i < (int)vec.size(); i++) {
        const Query & q = vec[i];
        if (q.leftPt == leftPt && q.rightPt == rightPt) {
            if (!merge(q.from, q.to) && getParent(q.from).second == getParent(q.to).second) {
                for (int i = leftPt; i <= rightPt; i++)
                    ans[i] = false;
                while ((int)stk.size() != stkSiz)
                    undo();
                return;    
            }
        } else {
            if (q.leftPt <= midPt)
                leftVec.push_back(Query{q.from, q.to, q.leftPt, min(q.rightPt, midPt)});
            if (q.rightPt > midPt)
                rightVec.push_back(Query{q.from, q.to, max(q.leftPt, midPt + 1), q.rightPt});
        }
    }

    if (leftPt == rightPt) {
        ans[leftPt] = true;
        while ((int)stk.size() > stkSiz)
            undo();
        return;
    }

    divideConquer(leftPt, midPt, leftVec);
    divideConquer(midPt + 1, rightPt, rightVec);
    while ((int)stk.size() > stkSiz)
        undo();
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int vertexNum, qNum, timeLen;
    cin >> vertexNum >> qNum >> timeLen;
    for (int i = 0; i < vertexNum; i++)
        parent[i] = i, dep[i] = 0, siz[i] = 1;
    while (qNum--) {
        int from, to, leftPt, rightPt;
        cin >> from >> to >> leftPt >> rightPt; 
        from--; to--; leftPt++;
        if (leftPt > rightPt)
            continue;
        ques.push_back(Query{from, to, leftPt, rightPt});
    }

    divideConquer(1, timeLen, ques);
    for (int i = 1; i <= timeLen; i++)
        cout << (ans[i] ? "Yes\n" : "No\n");
    return 0;
}

Codeforces 1217F

给定一个初始无边的含有 $n$ 个点的无向图(标号从 $1$ 开始),共 $q$ 次操作。操作有两种形式(其中 $last$ 代表上一次 $2$ 类操作的结果):

  • $1 \ x \ y \ (1 \le x, y \le n, \ x \neq y)$:在点 $(x + last - 1) \bmod (n + 1)$ 和点 $(y + last - 1) \bmod (n + 1)$ 间加一条无向边;
  • $2 \ x \ y \ (1 \le x, y \le n, \ x \neq y)$:查询点 $(x + last - 1) \bmod (n + 1)$ 和点 $(y + last - 1) \bmod (n + 1)$ 是否连通。
stack<pair<int *, int> > stk;
int parent[SIZE], siz[SIZE];

int getParent(int cntPt) {
    if (parent[cntPt] == cntPt)
        return cntPt;
    return getParent(parent[cntPt]);
}

bool merge(int fstPt, int sndPt) {
    fstPt = getParent(fstPt), sndPt = getParent(sndPt);
    if (fstPt == sndPt)
        return false;
    if (siz[fstPt] < siz[sndPt])
        swap(fstPt, sndPt);
    stk.push(make_pair(parent + sndPt, parent[sndPt])); parent[sndPt] = fstPt;
    stk.push(make_pair(siz + fstPt, siz[fstPt])); siz[fstPt] += siz[sndPt];
    return true;
}

void undo() {
    if (stk.empty())
        return;
    *stk.top().first = stk.top().second; stk.pop();
}

class Edge {
public:
    int op, from, to;
};
Edge edges[SIZE];

class Query {
public:
    int from, to, leftPt, rightPt;
};
vector<Query> ques; int vertexNum, last;
unordered_map<long long int, int> mp;
const auto p2ll = [](int x, int y) {
    tie(x, y) = make_tuple(min(x, y), max(x, y));
    return 1ll * x * SIZE + y; 
};

void divideConquer(int leftPt, int rightPt, const vector<Query> & vec) {
    int midPt = (leftPt + rightPt) >> 1, stkSiz = stk.size();
    const auto undoAll = [stkSiz]() { while ((int)stk.size() != stkSiz) undo(); };
    vector<Query> leftVec, rightVec;

    for (const auto & q : vec) {
        if (q.leftPt == leftPt && q.rightPt == rightPt) {
            if (!mp[p2ll(q.from, q.to)])
                continue;
            merge(q.from, q.to);
        } else {
            if (q.leftPt <= midPt)
                leftVec.push_back(Query{q.from, q.to, q.leftPt, min(q.rightPt, midPt)});
            if (q.rightPt > midPt)
                rightVec.push_back(Query{q.from, q.to, max(q.leftPt, midPt + 1), q.rightPt});
        }
    }
    
    if (leftPt == rightPt) {
        auto & e = edges[leftPt];
        e.from = (e.from + last) % vertexNum, e.to = (e.to + last) % vertexNum;
        
        if (e.op == 2) {
            last = (getParent(e.from) == getParent(e.to));
            cout << last;
        } else {
            mp[p2ll(e.from, e.to)] ^= 1;
        }
        undoAll();
        return;
    }

    divideConquer(leftPt, midPt, leftVec);
    divideConquer(midPt + 1, rightPt, rightVec);
    undoAll();
}

int main() {
    int qNum; cin >> vertexNum >> qNum;
    for (int i = 0; i < vertexNum; i++)
        parent[i] = i, siz[i] = 1;
    for (int i = 0; i < qNum; i++) {
        auto & e = edges[i]; cin >> e.op >> e.from >> e.to; e.from--; e.to--;
        const auto addQue = [i, qNum](int from, int to) {
            if (mp.find(p2ll(from, to)) == mp.end()) {
                if (i + 1 > qNum - 1)
                    return;
                mp[p2ll(from, to)] = ques.size();
                ques.push_back(Query{from, to, i + 1, qNum - 1});
            } else {
                ques[mp[p2ll(from, to)]].rightPt = max(ques[mp[p2ll(from, to)]].leftPt, i - 1);                    
                if (i + 1 > qNum - 1)
                    return;
                mp[p2ll(from, to)] = ques.size();
                ques.push_back(Query{from, to, i + 1, qNum - 1});
            }
        };
        if (e.op == 1) {
            int from = e.from, to = e.to;
            addQue(from, to);
            from = (from + 1) % vertexNum, to = (to + 1) % vertexNum;
            addQue(from, to);
        }
    }
    mp.clear(); last = 0;
    divideConquer(0, qNum - 1, ques);
    return 0;
}

整体二分

静态区间第 $k$ 小

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

#define SIZE 100100

typedef struct _Node {
    int val, id;
    bool operator < (const struct _Node & snd) const {
        return val < snd.val;
    }
} Node;

Node arr[SIZE];

typedef struct _Query {
    int qLeftPt, qRightPt, k;
    int id, cnt;
} Query;

Query qArr[SIZE], bkt[SIZE];
int ans[SIZE], bit[SIZE], len, qNum;

int lowbit(int n) {
    return n & -n;
}

void add(int pos, int val) {
    for (int i = pos; i <= len; i += lowbit(i))
        bit[i] += val;
}

int getPrefixSum(int pos) {
    int ret = 0;
    for (int i = pos; i > 0; i -= lowbit(i))
        ret += bit[i];
    return ret;
}

void divideConquer(int headPt, int tailPt, int leftPt, int rightPt) {
    if (leftPt == rightPt) {
        for (int i = headPt; i <= tailPt; i++)
            ans[qArr[i].id] = rightPt;
        return;
    }
    int midPt = (leftPt + rightPt) >> 1;
    // First element that is not smaller than leftPt
    int pos = lower_bound(arr + 0, arr + len, Node{leftPt, -1}) - arr;
    // Update BIT
    for (int i = pos; i < len && arr[i].val <= midPt; i++)
        add(arr[i].id + 1, 1);
	// Calculate cnt
    for (int i = headPt; i <= tailPt; i++)
        qArr[i].cnt = getPrefixSum(qArr[i].qRightPt + 1) - getPrefixSum(qArr[i].qLeftPt);
    // Restore BIT
    for (int i = pos; i < len && arr[i].val <= midPt; i++)
        add(arr[i].id + 1, -1);
	// Divide And Conquer
    int bktHeadPt = headPt, bktTailPt = tailPt;
    for (int i = headPt; i <= tailPt; i++) {
        if (qArr[i].cnt >= qArr[i].k) {
            bkt[bktHeadPt++] = qArr[i];
        } else {
            qArr[i].k -= qArr[i].cnt;
            bkt[bktTailPt--] = qArr[i];
        }
    }
    for (int i = headPt; i <= tailPt; i++)
        qArr[i] = bkt[i];
    if (bktHeadPt != headPt)
        divideConquer(headPt, bktHeadPt - 1, leftPt, midPt);
    if (bktTailPt != tailPt)
        divideConquer(bktTailPt + 1, tailPt, midPt + 1, rightPt);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    while (cin >> len >> qNum) {
        memset(bit, 0, sizeof(bit));
        int minVal = INT_MAX, maxVal = INT_MIN;
        for (int i = 0; i < len; i++) {
            cin >> arr[i].val;
            minVal = min(minVal, arr[i].val);
            maxVal = max(maxVal, arr[i].val);
            arr[i].id = i;
        }
        sort(arr + 0, arr + len);
        for (int i = 0; i < qNum; i++) {
            cin >> qArr[i].qLeftPt >> qArr[i].qRightPt >> qArr[i].k;
            qArr[i].qLeftPt--; qArr[i].qRightPt--;
            qArr[i].id = i; qArr[i].cnt = 0;
        }
        divideConquer(0, qNum - 1, minVal, maxVal);
        for (int i = 0; i < qNum; i++)
            cout << ans[i] << '\n';
    }
    return 0;
}

动态区间第 $k$ 小

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

#define SIZE 50010
#define QUE_SIZE 10010
#define OPR_SIZE 70010

// If k == 0: modify;
// qLeftPt = pos, qRightPt = val, cnt = type (-1: delete | 1: add)
typedef struct _Opr {
    int qLeftPt, qRightPt, k;   // pos, val, 0
    int qId, cnt;            // -1, type
} Opr;

Opr oprArr[OPR_SIZE], fstBkt[OPR_SIZE], sndBkt[OPR_SIZE];
int arr[SIZE], ans[QUE_SIZE], bit[SIZE], len;

int lowbit(int n) {
    return n & -n;
}

void add(int pos, int val) {
    for (int i = pos; i <= len; i += lowbit(i))
        bit[i] += val;
}

int prefixSum(int pos) {
    int ret = 0;
    for (int i = pos; i > 0; i -= lowbit(i))
        ret += bit[i];
    return ret;
}

void divideConquer(int headPt, int tailPt, int leftPt, int rightPt) {
    if (leftPt == rightPt) {
        for (int i = headPt; i <= tailPt; i++) {
            if (oprArr[i].k == 0)   // Skip modifications
                continue;
            ans[oprArr[i].qId] = rightPt;
        }
        return;
    }
    int midPt = (leftPt + rightPt) >> 1;
    // Update BIT && Calculate cnt
    for (int i = headPt; i <= tailPt; i++) {
        if (oprArr[i].k > 0)    // query
            oprArr[i].cnt = prefixSum(oprArr[i].qRightPt + 1) - prefixSum(oprArr[i].qLeftPt);
        else if (oprArr[i].qRightPt <= midPt)   // modify
            add(oprArr[i].qLeftPt + 1, oprArr[i].cnt);
    }
    // Restore BIT
    for (int i = headPt; i <= tailPt; i++)
        if (oprArr[i].k == 0 && oprArr[i].qRightPt <= midPt)
            add(oprArr[i].qLeftPt + 1, -oprArr[i].cnt);
    // Divide And Conquer
    int fstPt = 0, sndPt = 0;
    for (int i = headPt; i <= tailPt; i++) {
        if (oprArr[i].k > 0) {
            // query
            if (oprArr[i].k <= oprArr[i].cnt) {
                fstBkt[fstPt++] = oprArr[i];
            } else {
                oprArr[i].k -= oprArr[i].cnt;
                sndBkt[sndPt++] = oprArr[i];
            }
        } else {
            // modify
            if (oprArr[i].qRightPt <= midPt)
                fstBkt[fstPt++] = oprArr[i];
            else
                sndBkt[sndPt++] = oprArr[i];
        }
    }
    for (int i = 0; i < fstPt; i++)
        oprArr[headPt + i] = fstBkt[i];
    for (int i = 0; i < sndPt; i++)
        oprArr[headPt + fstPt + i] = sndBkt[i];
    if (fstPt > 0)
        divideConquer(headPt, headPt + fstPt - 1, leftPt, midPt);
    if (sndPt > 0)
        divideConquer(headPt + fstPt, tailPt, midPt + 1, rightPt);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int caseNum;
    cin >> caseNum;
    while (caseNum--) {
        memset(bit, 0, sizeof(bit));
        int qNum, oprPt = 0, qPt = 0, minVal = INT_MAX, maxVal = 0;
        cin >> len >> qNum;
        for (int i = 0; i < len; i++) {
            // treat original array as insertions
            cin >> arr[i];
            minVal = min(minVal, arr[i]);
            maxVal = max(maxVal, arr[i]);
            oprArr[oprPt++] = {i, arr[i], 0, -1, 1};
        }
        for (int i = 0; i < qNum; i++) {
            char opr; cin >> opr;
            if (opr == 'Q') {	// Query
                int qLeftPt, qRightPt, k;
                cin >> qLeftPt >> qRightPt >> k;
                qLeftPt--, qRightPt--;
                oprArr[oprPt++] = {qLeftPt, qRightPt, k, qPt++, 0};
            } else if (opr == 'C') {	// Change
                int pos, val;
                cin >> pos >> val;
                pos--;
                // delete
                oprArr[oprPt++] = {pos, arr[pos], 0, -1, -1};
                // insert
                oprArr[oprPt++] = {pos, val, 0, -1, 1};
                arr[pos] = val;
                minVal = min(minVal, val);
                maxVal = max(maxVal, val);
            }
        }
        divideConquer(0, oprPt - 1, minVal, maxVal);
        for (int i = 0; i < qPt; i++)
            cout << ans[i] << '\n';
    }
    return 0;
}

分治解决动态 MST

$n$ 个点,$m$ 条有权边,$q$ 次操作。每次操作修改一条边的边权,动态维护 MST 大小。

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

#define VERTEX_SIZE 20010
#define EDGE_SIZE 50010

typedef struct _Query {
    int id, val;
} Query;

Query qArr[EDGE_SIZE];

typedef struct _Edge {
    int id, from, to, len;
    bool operator < (const struct _Edge & snd) const {
        return len < snd.len;
    }
} Edge;

vector<Edge> edge[30], ret, tmp;

int id[EDGE_SIZE], cntLen[EDGE_SIZE];
long long int ans[EDGE_SIZE];

/* Disjoint Set */
int parent[VERTEX_SIZE], siz[VERTEX_SIZE];

int getParent(int cntPt) {
    if (parent[cntPt] == cntPt)
        return cntPt;
    parent[cntPt] = getParent(parent[cntPt]);
    return parent[cntPt];
}

bool merge(int fstPt, int sndPt) {
    fstPt = getParent(fstPt); sndPt = getParent(sndPt);
    if (fstPt == sndPt)
        return false;
    if (siz[fstPt] < siz[sndPt])
        swap(fstPt, sndPt);
    parent[sndPt] = fstPt;
    siz[fstPt] += siz[sndPt];
    return true;
}

void clear(const vector<Edge> & vec) {
    for (const auto & e : vec) {
        parent[e.from] = e.from;
        parent[e.to] = e.to;
        siz[e.from] = 1; siz[e.to] = 1;
    }
}

/* Divide And Conquer */
void relabel(vector<Edge> & vec) {
    int pt = 0;
    for (const auto & e : vec)
        id[e.id] = pt++;
}

void reduction() { 
    tmp.clear(); clear(ret); 
    sort(ret.begin(), ret.end());
    for (const auto & e : ret)
        if (e.len == INT_MAX || merge(e.from, e.to))
            tmp.push_back(e);

    relabel(tmp); ret = tmp;
}

void contraction(long long int & val) { 
    // Reduction
    tmp.clear(); clear(ret);
    sort(ret.begin(), ret.end());
    for (const auto & e : ret)
        if (merge(e.from, e.to)) 
            tmp.push_back(e);
    // Contraction
    clear(tmp); sort(tmp.begin(), tmp.end());
    for (const auto & e : tmp)
        if (e.len != INT_MIN && merge(e.from, e.to))
            val += e.len;
    tmp.clear();
    for (const auto & e : ret) {
        int from = getParent(e.from), to = getParent(e.to);
        tmp.push_back({e.id, from, to, e.len});
    }
    relabel(tmp); ret = tmp;
} 

void divideConquer(int headPt, int tailPt, int dep, long long int val) {
    if (headPt == tailPt)
        cntLen[qArr[headPt].id] = qArr[headPt].val; 
    // Recover
    ret = edge[dep]; relabel(ret);
    for (auto & e : ret)
        e.len = cntLen[e.id];
    if (headPt == tailPt) {
        ans[headPt] = val; clear(ret);
        sort(ret.begin(), ret.end());
        for (const auto & e : ret)
            if (merge(e.from, e.to))
                ans[headPt] += e.len;
        return;
    }
    for (int i = headPt; i <= tailPt; i++)
        ret[id[qArr[i].id]].len = INT_MIN;
    contraction(val);
    for (int i = headPt; i <= tailPt; i++)
        ret[id[qArr[i].id]].len = INT_MAX;
    reduction();
    // Pushdown
    edge[dep + 1] = ret;
    int midPt = (headPt + tailPt) >> 1;
    divideConquer(headPt, midPt, dep + 1, val);
    divideConquer(midPt + 1, tailPt, dep + 1, val);
} 

int main() {
    int vertexNum, edgeNum, qNum;
    cin >> vertexNum >> edgeNum >> qNum;
    for (int i = 0; i < edgeNum; i++) {
        int from, to, len; cin >> from >> to >> len;
        from--; to--; cntLen[i] = len;
        edge[0].push_back({i, from, to, len});
    }
    for (int i = 0; i < qNum; i++) {
        cin >> qArr[i].id >> qArr[i].val;
        qArr[i].id--;
    }

    divideConquer(0, qNum - 1, 0, 0);
    for (int i = 0; i < qNum; i++)
        cout << ans[i] << '\n';
    return 0;
}

4463 字

2019-11-18 09:10 +0800