1. 程式人生 > 實用技巧 >「APIO2019」橋樑 題解

「APIO2019」橋樑 題解

先講下部分分怎麼搞。

有個非常暴力的暴力做法:

對於每一個詢問,把邊權大於 \(w_j\) 的邊加入,並查集維護聯通塊即可。

時間複雜度 \(\mathcal{O(qm)}\),可以過 \(\mathrm{Subtask\ 1}\)

\(t_i=2\) 的時候,可以直接 kruskal 重構樹,可以過 \(\mathrm{Subtask \ 4}\)

\(\rm Subtask \ 2\) 是一個鏈的結構,發現問題轉為 \(\max\limits_{l,r,i\in[l,r] \& d_i>=w_j}\{ r-l+1 \}\)

然後就可以用線段樹做一下。

\(\rm Subtask \ 3,5\)

...不會做,告辭(

於是就可以收穫 \(43 \rm pts\)


至於正解...

可以把詢問分塊,設塊長為 \(S\),把每一條邊分成三種去做,一種是塊內沒修改過的,一種是塊內修改過,時間在詢問前的,另一種是塊內修改過,時間在詢問後的。

前一種邊可以直接與詢問排序去做,時間複雜度 \(\mathcal O(\frac{q}{S}m \log m)\)

第二種邊,第三種邊可以列舉塊內修改,用可撤銷化並查集去做,時間複雜度是 \(\mathcal O(\frac{q}{S}S^2 \log n)\)

至於塊長多大最優,人肉二分得出好像是 \(\sqrt{m \log n}\)

交 LOJ ,直接 AC 了,然後再交洛谷,TLE 44 pts....

\(\sf\color{black}{F}\color{red}{zzzz}\) 說這題還有兩個優化,看了一眼題解,發現第一種邊在排序的時候完全可以先排序再歸併,流程如下:

  1. 在解決詢問之前先把邊排序一遍
  2. 把排序後的邊塞進塊裡解決詢問,把詢問排序,沒修改的邊拉出來和詢問做一次歸併排序,時間複雜度 \(\mathcal{O(\frac{q}{S}m)}\)
  3. 解決當前塊內的詢問後,把塊內修改的邊拉出來排序,沒有修改的邊也拉出來,和修改的邊做一次歸併排序,時間複雜度 \(\mathcal{O}(\frac{q}{S}m)\)

看起來好像優化了時間複雜度但其實只是卡常,因為時間複雜度主要在可撤銷化並查集上...

總而言之這題是一道不錯的資料結構題,也許能放進 NOIP 提高組並查集講課裡(

如果看不懂可以看程式碼,程式碼如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int u[N], v[N], d[N], vis[N], n, m;
struct query {
    int type, val, id;
    query() {}
    query(int _t, int _v, int _id) {
        type = _t;
        val = _v;
        id = _id;
    }
    bool operator<(const query &b) const { return val == b.val ? type < b.type : val > b.val; }
};
void merge(query *A, query *B, query *f, int n, int m) {
    int i = 1, j = 1, cnt = 0;
    while (i <= n && j <= m)
        if (A[i] < B[j])
            f[++cnt] = A[i++];
        else
            f[++cnt] = B[j++];
    while (i <= n) f[++cnt] = A[i++];
    while (j <= m) f[++cnt] = B[j++];
}
query a[N << 1], A[N << 1], B[N << 1], F[N << 1];
int type[N], b[N], r[N], s[N], w[N], ans[N], cnt;
int f[N], sz[N];
int find(int x) {
    while (x != f[x]) x = f[x];
    return x;
}
int st[N], top = 0;
void solve(int L, int R) {
    cnt = 0;
    int cnt1 = 0, cnt2 = 0;
    for (int i = 1; i <= n; ++i) f[i] = i, sz[i] = 1;
    for (int i = L; i <= R; ++i)
        if (type[i] == 1)
            vis[b[i]] = i;
        else
            A[++cnt1] = query(1, w[i], i);
    sort(A + 1, A + cnt1 + 1);
    for (int i = 1; i <= m; ++i)
        if (vis[F[i].id] == 0)
            B[++cnt2] = F[i];
    merge(A, B, a, cnt1, cnt2);
    cnt = cnt1 + cnt2;
    for (int i = L; i <= R; ++i) vis[b[i]] = 0;
    for (int i = 1; i <= cnt; ++i) {
        // if(L==3) cout<<a[i].type<<" "<<a[i].id<<" "<<d[a[i].id]<<endl;
        if (a[i].type == 0) {
            int x = find(u[a[i].id]), y = find(v[a[i].id]);
            if (x == y)
                continue;
            if (sz[x] >= sz[y])
                swap(x, y);
            f[x] = y;
            sz[y] += sz[x];
        } else {
            top = 0;
            for (int j = L; j <= a[i].id; ++j)
                if (type[j] == 1)
                    vis[b[j]] = r[j];
            for (int j = L; j <= a[i].id; ++j)
                if (type[j] == 1 && vis[b[j]] >= a[i].val) {
                    int x = find(u[b[j]]), y = find(v[b[j]]);
                    if (x == y)
                        continue;
                    if (sz[x] >= sz[y])
                        swap(x, y);
                    st[++top] = x;
                    f[x] = y;
                    sz[y] += sz[x];
                }
            for (int j = a[i].id; j <= R; ++j)
                if (type[j] == 1 && d[b[j]] >= a[i].val && vis[b[j]] == 0) {
                    // if(i==3) cout<<j<<" Q\n";
                    int x = find(u[b[j]]), y = find(v[b[j]]);
                    if (x == y)
                        continue;
                    if (sz[x] >= sz[y])
                        swap(x, y);
                    st[++top] = x;
                    f[x] = y;
                    sz[y] += sz[x];
                }
            int x = find(s[a[i].id]);
            ans[a[i].id] = sz[x];
            while (top > 0) sz[f[st[top]]] -= sz[st[top]], f[st[top]] = st[top], --top;
            for (int j = L; j <= a[i].id; ++j)
                if (type[j] == 1)
                    vis[b[j]] = 0;
        }
    }
    for (int i = L; i <= R; ++i)
        if (type[i] == 1)
            vis[b[i]] = 0;
}
int L[N], R[N];
int rd() {
    int x = 0;
    char ch = getchar();
    while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
    return x;
}
void write(int x) {
    if (x < 0)
        return putchar('-'), write(-x);
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}
int main() {
    n = rd();
    m = rd();
    for (int i = 1; i <= m; ++i) u[i] = rd(), v[i] = rd(), d[i] = rd();
    for (int i = 1; i <= m; ++i) F[i] = query(0, d[i], i);
    sort(F + 1, F + m + 1);
    int q;
    cin >> q;
    for (int i = 1; i <= q; ++i) {
        cin >> type[i];
        if (type[i] == 1)
            b[i] = rd(), r[i] = rd();
        else
            s[i] = rd(), w[i] = rd();
    }
    int len = 1000, cnt = q / len;
    for (int i = 1; i <= cnt; ++i) L[i] = R[i - 1] + 1, R[i] = L[i] + len - 1;
    if (R[cnt] < q)
        ++cnt, L[cnt] = R[cnt - 1] + 1, R[cnt] = q;
    for (int i = 1; i <= cnt; ++i) {
        solve(L[i], R[i]);
        for (int j = L[i]; j <= R[i]; ++j)
            if (type[j] == 1)
                d[b[j]] = r[j];
        int cnt1 = 0, cnt2 = 0;
        for (int j = L[i]; j <= R[i]; ++j)
            if (type[j] == 1)
                vis[b[j]] = 1;
        for (int j = 1; j <= m; ++j)
            if (vis[F[j].id] == 0)
                A[++cnt1] = F[j];
            else
                B[++cnt2] = query(0, d[F[j].id], F[j].id);
        sort(B + 1, B + cnt2 + 1);
        merge(A, B, F, cnt1, cnt2);
        for (int j = L[i]; j <= R[i]; ++j)
            if (type[j] == 1)
                vis[b[j]] = 0;
    }
    for (int i = 1; i <= q; ++i)
        if (type[i] == 2)
            write(ans[i]), putchar('\n');
    return 0;
}

對於我來說,這題是典型的口胡 3 分鐘,寫程式碼 3 小時 /kk