Editorial for PVHOI3 - Bài 3: Đếm chu trình


Remember to use this editorial only when stuck, and not to copy-paste code from it. Please be respectful to the problem author and editorialist.

Submitting an official solution before solving the problem yourself is a bannable offence.

Subtask 1

Tutorial

Với mỗi truy vấn, bỏ đi 2 cạnh trong danh sách và thêm 2 cạnh mới vào theo yêu cầu, sau đó duyệt hết tất cả các tập cạnh và đếm số lượng tập thoả mãn là chu trình.

Độ phức tạp: \(O(q \cdot 2^n \cdot n)\)

Solution
C++
#include <bits/stdc++.h>

using namespace std;

const int MOD = (int)1e9 + 19972207;
int q, mul, add;
int cur;

const int N = 20;
int numNode;
pair<int,int> edges[N];
int mark[N];
int fa[N], cnt[N], res;

int root(int x) {
    return fa[x] < 0 ? x : fa[x] = root(fa[x]);
}

void unite(int u, int v) {
    u = root(u);
    v = root(v);
    if (u == v) {
        return;
    }
    if (fa[u] > fa[v]) {
        swap(u, v);
    }
    fa[u] += fa[v];
    fa[v] = u;
}

void process() {
    memset(fa, -1, sizeof fa);
    memset(cnt, 0, sizeof cnt);
    for (int i = 1; i <= numNode+1; ++i) {
        if (mark[i] == 1) {
            cnt[edges[i].first]++;
            cnt[edges[i].second]++;
            unite(edges[i].first, edges[i].second);
        }
    }
    int last = -1;
    for (int i = 1; i <= numNode; ++i) {
        if (cnt[i] != 0 && cnt[i] != 2) {
            return;
        }
        if (cnt[i] == 2) {
            if (last != -1 && last != root(i)) {
                return;
            }
            last = root(i);
        }
    }
    res++;
}

void backtrack(int p) {
    if (p == numNode+2) {
        process();
        return;
    }
    backtrack(p+1);
    if (mark[p] != -1) {
        mark[p] = 1;
        backtrack(p+1);
        mark[p] = 0;
    }
}

int query(int e1, int u1, int v1, int e2, int u2, int v2) {
    memset(mark, 0, sizeof mark);
    mark[e1] = -1;
    mark[e2] = -1;
    edges[numNode] = make_pair(u1, v1);
    edges[numNode+1] = make_pair(u2, v2);
    res = 0;
    backtrack(1);
    return res-1;
}

int getRandom(int n) {
    cur = (1LL * mul * cur + add) % MOD;
    return 1 + cur % n;
}
int gspvhcute(void) {
    int res = 0;
    for (int i = 1; i <= q; i++) {
        int e1 = getRandom(numNode - 1);
        int u1 = getRandom(numNode); int v1 = getRandom(numNode);
        int e2 = getRandom(numNode - 1);
        int u2 = getRandom(numNode); int v2 = getRandom(numNode);
        int tmp = e1 == e2 || u1 == v1 || u2 == v2 
                ? MOD - 1 : query(e1, u1, v1, e2, u2, v2);
        res = (227LL * res + tmp) % MOD;
    }
    return res;
}

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    freopen("chutrinh.inp", "r", stdin);
    freopen("chutrinh.out", "w", stdout);

    int t; cin >> t;
    cin >> numNode;
    for (int i = 1; i < numNode; ++i) {
        cin >> edges[i].first >> edges[i].second;
    }
    cin >> q >> cur >> mul >> add;

    cout << gspvhcute();

    return 0;
}

Các subtask sau, ta có nhận xét: Chọn đỉnh \(1\) là gốc của cây, khi bỏ đi hai cạnh, cây sẽ bị tách ra thành ba cây con.

Ví dụ, khi bỏ đi hai cạnh \((x_1, y_1)\)\((x_2, y_2)\) với \(x_1\) là cha của \(y_1\)\(x_2\) là cha của \(y_2\), thì cây sẽ tách thành ba cây con gốc \(1, \ x_1,\)\(x_2\).

Thêm hai cạnh \((u_1, v_1)\)\((u_2, v_2)\), gọi \(A(x)\) là cây con trong ba cây con gốc \(1, \ x_1, \text{hoặc} \ x_2\) chứa đỉnh x, xét các trường hợp sau:

  • Nếu \(A(u_1) \neq A(v_1)\)\(A(u_2) \neq A(v_2)\),
    • Nếu cặp \((A(u_1),A(v_1))\) khác cặp \((A(u_2),A(v_2))\), số lượng chu trình đơn là \(0\) vì đồ thị sẽ thành một cái cây.
    • Nếu cặp \((A(u_1),A(v_1))\) giống cặp \((A(u_2),A(v_2))\), số lượng chu trình đơn là \(1\) vì khi đó đồ thị sẽ gồm 2 cây và một thành phần có số lượng đỉnh bằng số lượng cạnh (đồ thị có đúng \(1\) chu trình đơn).
  • Nếu \(A(u_1) \neq A(v_1)\)\(A(u_2) = A(v_2)\), thì số lượng chu trình đơn là \(1\) vì khi thêm cạnh \((u_1,v_1)\) vào, đồ thị sẽ gồm hai cây, khi thêm cạnh \((u_2,v_2)\) vào một trong hai cây sẽ tạo ra đúng \(1\) chu trình đơn.
  • Nếu \(A(u_1) = A(v_1)\)\(A(u_2) = A(v_2)\),
    • Nếu \(A(u_1) \neq A(u_2)\), thì số lượng chu trình đơn là \(2\) vì đồ thị bây giờ gồm \(1\) cây và \(2\) thành phần liên thông có số đỉnh bằng số cạnh.
    • Nếu \(A(u_1) = A(u_2)\), giả sử chưa thêm hai cạnh vào,
      • Nếu đường đi từ \(u_1\) đến \(v_1\) có giao với đường đi từ \(u_2\) đến \(v_2\) ít nhất một cạnh, thì số lượng chu trình đơn là \(3\), một chu trình có chứa cạnh \((u_1,v_1)\), một chu trình có chứa cạnh \((u_2,v_2)\), và một chu trình có chứa cả hai cạnh \((u_1,v_1)\)\((u_2,v_2)\).
      • Nếu hai đường đi không có giao cạnh, thì số lượng chu trình đơn là \(2\), một chu trình có chứa cạnh \((u_1,v_1)\), và một chu trình có chứa cạnh \((u_2,v_2)\).

Ở các subtask sẽ có các cách khác nhau để kiểm tra tồn tại cạnh giao giữa hai đường đi.

Subtask 2

Tutorial

Cây có dạng chuổi thẳng, vậy nên ta có thể kiểm tra tương tự như kiểu tra hai đoạn thẳng giao nhau trên mảng một chiều.

Độ phức tạp: \(O(n+q)\)

Solution
C++
#include <bits/stdc++.h>

using namespace std;

const int MOD = (int)1e9 + 19972207;
int q, mul, add;
int cur;

const int N = 15e4+7;
int numNode;
vector<int> adj[N];
pair<int,int> edges[N];
int nodes[N];

void buildGraph() {
    int rt = -1; 
    for (int i = 1; i <= numNode; ++i) {
        if (adj[i].size() == 1) {
            rt = i;
            break;
        }
    }
    assert(rt != -1);
    int siz = 0;
    int last = 0;
    for (int i = 1; i <= numNode; ++i) {
        nodes[rt] = ++siz;
        for (int j = 0; j < 2; ++j) {
            if (adj[rt][j] != last) {
                last = rt;
                rt = adj[rt][j];
                break;
            }
        }
    }
}

void updateAnc(int &a, int x, int u, int v) {
    if (x >= u) a = max(a, u);
    if (x >= v) a = max(a, v);
}

int query(int e1, int u1, int v1, int e2, int u2, int v2) {
    int x1, y1, x2, y2;
    tie(x1, y1) = edges[e1];
    tie(x2, y2) = edges[e2];
    x1 = nodes[x1]; y1 = nodes[y1];
    x2 = nodes[x2]; y2 = nodes[y2];
    if (x1 < y1) swap(x1, y1);
    if (x2 < y2) swap(x2, y2);
    u1 = nodes[u1]; v1 = nodes[v1];
    u2 = nodes[u2]; v2 = nodes[v2];
    int pu1, pv1, pu2, pv2;
    pu1 = pv1 = pu2 = pv2 = 1;
    updateAnc(pu1, u1, x1, x2);
    updateAnc(pv1, v1, x1, x2);
    updateAnc(pu2, u2, x1, x2);
    updateAnc(pv2, v2, x1, x2);
    if (pu1 > pv1) swap(pu1, pv1);
    if (pu2 > pv2) swap(pu2, pv2);
    if (pu2 != pv2) {
        swap(pu1, pu2);
        swap(pv1, pv2);
        swap(u1, u2);
        swap(v1, v2);
    }
    if (pu1 != pv1) {
        if (pu2 == pv2) return 1;
        else return pu1 == pu2 && pv1 == pv2;
    } else {
        if (pu1 != pu2) {
            return 2;
        } else {
            if (u1 > v1) swap(u1, v1);
            if (u2 > v2) swap(u2, v2);
            if (max(u1, u2) < min(v1, v2)) {
                return 3;
            } else {
                return 2;
            }
        }
    }
    assert(false);
    return 0;
}

int getRandom(int n) {
    cur = (1LL * mul * cur + add) % MOD;
    return 1 + cur % n;
}
int gspvhcute(void) {
    int res = 0;
    for (int i = 1; i <= q; i++) {
        int e1 = getRandom(numNode - 1);
        int u1 = getRandom(numNode); int v1 = getRandom(numNode);
        int e2 = getRandom(numNode - 1);
        int u2 = getRandom(numNode); int v2 = getRandom(numNode);
        int tmp = e1 == e2 || u1 == v1 || u2 == v2 
                ? MOD - 1 : query(e1, u1, v1, e2, u2, v2);
        res = (227LL * res + tmp) % MOD;
    }
    return res;
}

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    freopen("chutrinh.inp", "r", stdin);
    freopen("chutrinh.out", "w", stdout);

    int t; cin >> t;
    cin >> numNode;
    for (int i = 1; i < numNode; ++i) {
        cin >> edges[i].first >> edges[i].second;
        adj[edges[i].first].push_back(edges[i].second);
        adj[edges[i].second].push_back(edges[i].first);
    }
    cin >> q >> cur >> mul >> add;
    buildGraph();
    cout << gspvhcute();
    return 0;
}

Subtask 3

Tutorial

DFS/BFS, đánh dấu các cạnh nằm trên đường đi từ \(u_1\) đến \(v_1\) và từ \(u_2\) đến \(v_2\), kiểm tra có tồn tại cạnh được đánh dấu ở cả hai con đường.

Độ phức tạp: \(O(q \cdot n)\)

Solution
C++
#include <bits/stdc++.h>

using namespace std;

struct Edge {
    int x, y;
    Edge() {
        x = y = 0;
    }
    Edge(int _x, int _y) {
        x = _x; y = _y;
    }
};

const int MOD = (int)1e9 + 19972207;
int q, mul, add;
int cur;

const int N = 3007;
int numNode;
Edge edges[N];
vector<int> adj[N];
int in[N], out[N], tim;
bool mark[2][N];

void dfs(int u, int p) {
    in[u] = ++tim;
    for (int e : adj[u]) {
        int v = u ^ edges[e].x ^ edges[e].y;
        if (v!=p) {
            dfs(v, u);
        }
    }
    out[u] = tim;
}

void dfs(int u, int p, int dest, bool *mk) {
    if (u == dest) {
        throw 1;
    }
    for (int e : adj[u]) {
        int v = u ^ edges[e].x ^ edges[e].y;
        if (v != p) {
            mk[e] = true;
            dfs(v, u, dest, mk);
            mk[e] = false;
        }
    }
}

bool isAnc(int u, int v) {
    return in[u] <= in[v] && in[v] <= out[u];
}

void updateAnc(int &a, int x, int u, int v) {
    if (isAnc(u, x) && isAnc(a, u)) a = u;
    if (isAnc(v, x) && isAnc(a, v)) a = v;
}

int query(int e1, int u1, int v1, int e2, int u2, int v2) {
    int x1, y1, x2, y2;
    x1 = edges[e1].x; y1 = edges[e1].y;
    x2 = edges[e2].x; y2 = edges[e2].y;
    if (isAnc(x1, y1)) swap(x1, y1);
    if (isAnc(x2, y2)) swap(x2, y2);
    int pu1, pv1, pu2, pv2;
    pu1 = pv1 = pu2 = pv2 = 1;
    updateAnc(pu1, u1, x1, x2);
    updateAnc(pv1, v1, x1, x2);
    updateAnc(pu2, u2, x1, x2);
    updateAnc(pv2, v2, x1, x2);
    if (pu1 > pv1) swap(pu1, pv1);
    if (pu2 > pv2) swap(pu2, pv2);
    if (pu2 != pv2) {
        swap(pu1, pu2);
        swap(pv1, pv2);
        swap(u1, u2);
        swap(v1, v2);
    }
    if (pu1 != pv1) {
        if (pu2 == pv2) return 1;
        else return pu1 == pu2 && pv1 == pv2;
    } else {
        if (pu1 != pu2) {
            return 2;
        } else { // pu1 == pu2
            memset(mark[0], false, numNode * sizeof(bool));
            try {
                dfs(u1, 0, v1, mark[0]);
            } catch(...) {}
            memset(mark[1], false, numNode * sizeof(bool));
            try {
                dfs(u2, 0, v2, mark[1]);
            } catch(...) {}
            for (int i = 1; i < numNode; ++i) {
                if (mark[0][i] && mark[1][i]) {
                    return 3;
                }
            }
            return 2;
        }
    }
    assert(false);
    return 0;
}

int getRandom(int n) {
    cur = (1LL * mul * cur + add) % MOD;
    return 1 + cur % n;
}
int gspvhcute(void) {
    int res = 0;
    for (int i = 1; i <= q; i++) {
        int e1 = getRandom(numNode - 1);
        int u1 = getRandom(numNode); int v1 = getRandom(numNode);
        int e2 = getRandom(numNode - 1);
        int u2 = getRandom(numNode); int v2 = getRandom(numNode);
        int tmp = e1 == e2 || u1 == v1 || u2 == v2 
                ? MOD - 1 : query(e1, u1, v1, e2, u2, v2);
        res = (227LL * res + tmp) % MOD;
    }
    return res;
}

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    freopen("chutrinh.inp", "r", stdin);
    freopen("chutrinh.out", "w", stdout);

    int t; cin >> t;
    cin >> numNode;
    for (int i = 1; i < numNode; ++i) {
        cin >> edges[i].x >> edges[i].y;
        adj[edges[i].x].push_back(i);
        adj[edges[i].y].push_back(i);
    }
    cin >> q >> cur >> mul >> add;

    dfs(1, 0);
    cout << gspvhcute();
    return 0;
}

Subtask 4

Tutorial

Gọi \(\texttt{lca}(u, v)\) là tổ tiên chung gần nhất của \(u\)\(v\) (lưu ý: một đỉnh cũng là tổ tiên của chính nó), chia đường đi từ \(u_1\) đến \(v_1\) thành hai đường từ \(u_1\) đến \(\texttt{lca}(u_1,v_1)\)\(v_1\) đến \(\texttt{lca}(u_1,v_1)\), đánh dấu các cạnh trên đường đi, tương tự với \((u_2,v_2)\), kiểm tra có tồn tại cạnh chung ở hai đường đi.

Độ phức tạp: \(O(q \cdot n)\)

Solution
C++
#include <bits/stdc++.h>

using namespace std;

struct Edge {
    int x, y;
    Edge() {
        x = y = 0;
    }
    Edge(int _x, int _y) {
        x = _x; y = _y;
    }
};

const int MOD = (int)1e9 + 19972207;
int q, mul, add;
int cur;

const int N = 3007;
int numNode;
Edge edges[N];
vector<int> adj[N];
int in[N], out[N], tim;
int depth[N], par[N];
bool mark[N];

void dfs(int u, int p) {
    in[u] = ++tim;
    for (int e : adj[u]) {
        int v = u ^ edges[e].x ^ edges[e].y;
        if (v!=p) {
            depth[v] = depth[u] + 1;
            par[v] = u;
            dfs(v, u);
        }
    }
    out[u] = tim;
}

bool isAnc(int u, int v) {
    return in[u] <= in[v] && in[v] <= out[u];
}

void updateAnc(int &a, int x, int u, int v) {
    if (isAnc(u, x) && isAnc(a, u)) a = u;
    if (isAnc(v, x) && isAnc(a, v)) a = v;
}

bool goUp(int u, int v) {
    if (depth[u] < depth[v]) {
        swap(u, v);
    }
    while (depth[u] > depth[v]) {
        if (mark[u]) return true;
        mark[u] = true;
        u = par[u];
    }
    while (u != v) {
        if (mark[u]) return true;
        if (mark[v]) return true;
        mark[u] = true;
        mark[v] = true;
        u = par[u];
        v = par[v];
    }
    return false;
}

int query(int e1, int u1, int v1, int e2, int u2, int v2) {
    int x1, y1, x2, y2;
    x1 = edges[e1].x; y1 = edges[e1].y;
    x2 = edges[e2].x; y2 = edges[e2].y;
    if (isAnc(x1, y1)) swap(x1, y1);
    if (isAnc(x2, y2)) swap(x2, y2);
    int pu1, pv1, pu2, pv2;
    pu1 = pv1 = pu2 = pv2 = 1;
    updateAnc(pu1, u1, x1, x2);
    updateAnc(pv1, v1, x1, x2);
    updateAnc(pu2, u2, x1, x2);
    updateAnc(pv2, v2, x1, x2);
    if (pu1 > pv1) swap(pu1, pv1);
    if (pu2 > pv2) swap(pu2, pv2);
    if (pu2 != pv2) {
        swap(pu1, pu2);
        swap(pv1, pv2);
        swap(u1, u2);
        swap(v1, v2);
    }
    if (pu1 != pv1) {
        if (pu2 == pv2) return 1;
        else return pu1 == pu2 && pv1 == pv2;
    } else {
        if (pu1 != pu2) {
            return 2;
        } else { // pu1 == pu2
            memset(mark, false, (numNode+1) * sizeof(bool));
            goUp(u1, v1);
            if (goUp(u2, v2)) {
                return 3;
            } else {
                return 2;
            }
        }
    }
    assert(false);
    return 0;
}

int getRandom(int n) {
    cur = (1LL * mul * cur + add) % MOD;
    return 1 + cur % n;
}
int gspvhcute(void) {
    int res = 0;
    for (int i = 1; i <= q; i++) {
        int e1 = getRandom(numNode - 1);
        int u1 = getRandom(numNode); int v1 = getRandom(numNode);
        int e2 = getRandom(numNode - 1);
        int u2 = getRandom(numNode); int v2 = getRandom(numNode);
        int tmp = e1 == e2 || u1 == v1 || u2 == v2 
                ? MOD - 1 : query(e1, u1, v1, e2, u2, v2);
        res = (227LL * res + tmp) % MOD;
    }
    return res;
}

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    freopen("chutrinh.inp", "r", stdin);
    freopen("chutrinh.out", "w", stdout);

    int t; cin >> t;
    cin >> numNode;
    for (int i = 1; i < numNode; ++i) {
        cin >> edges[i].x >> edges[i].y;
        adj[edges[i].x].push_back(i);
        adj[edges[i].y].push_back(i);
    }
    cin >> q >> cur >> mul >> add;

    dfs(1, 0);
    cout << gspvhcute();
    return 0;
}

Subtask 5

Tutorial

Gọi \(l_1\)\(\texttt{lca}(u_1,v_1)\), \(l_2\)\(\texttt{lca}(u_2,v_2)\), ta sẽ chia đường đi từ \(u_1\) đến \(v_1\) thành đường đi từ \(l_1\) đến \(u_1\) và đường đi từ \(l_1\) đến \(v_1\), tương tự với \((u_2,v_2)\).

Xét bài toán kiểm tra tồn tại cạnh chung ở hai đường đi \((x,y)\)\((u,v)\) với \(x\) là tổ tiên của \(y\)\(u\) là tổ tiên của \(v\):

  • nếu \(u = v\) hoặc \(x = y\) thì hai đường đi không giao nhau.
  • Nếu \(x\)\(u\) không có quan hệ tổ tiên, hai đường đi không giao nhau.
  • Ngược lại, giả sử \(u\) là tổ tiên của \(x\), nếu \(x\) nằm trên đường đi từ \(u\) đến đỉnh cha của \(v\)\(\texttt{lca}(y,v) \neq u\) thì hai đường đi có giao nhau ít nhất một cạnh.

Như vậy chỉ cần kiểm tra đường đi từ \(l_1\) đến \(u_1\) và từ \(l_1\) đến \(v_1\) có giao với đường đi từ \(l_2\) đến \(u_2\) và từ \(l_2\) đến \(v_2\) (kiểm tra \(4\) cặp đường đi) hay không.

Độ phức tạp: \(O((n+q) \cdot \log(n))\)

Solution
C++
#include <bits/stdc++.h>

using namespace std;

struct Edge {
    int x, y;
    Edge() {
        x = y = 0;
    }
    Edge(int _x, int _y) {
        x = _x; y = _y;
    }
};

const int MOD = (int)1e9 + 19972207;
int q, mul, add;
int cur;

const int N = 15e4+7;
const int LOG = 20;
int numNode;
Edge edges[N];
vector<int> adj[N];
int in[N], out[N], tim;
int depth[N], anc[N][LOG];

void dfs(int u, int p) {
    in[u] = ++tim;
    for (int i = 1; i < LOG; ++i) {
        anc[u][i] = anc[anc[u][i-1]][i-1];
    }
    for (int e : adj[u]) {
        int v = u ^ edges[e].x ^ edges[e].y;
        if (v!=p) {
            depth[v] = depth[u] + 1;
            anc[v][0] = u;
            dfs(v, u);
        }
    }
    out[u] = tim;
}

inline bool isAnc(int u, int v) {
    return in[u] <= in[v] && in[v] <= out[u];
}

inline bool isStrictAnc(int u, int v) {
    return u != v && isAnc(u, v);
}

void updateAnc(int &a, int x, int u, int v) {
    if (isAnc(u, x) && isAnc(a, u)) a = u;
    if (isAnc(v, x) && isAnc(a, v)) a = v;
}

int lca(int u, int v) {
    if (depth[u] < depth[v]) {
        swap(u, v);
    }
    for (int i = LOG-1; i >= 0; --i) {
        if (depth[anc[u][i]] >= depth[v]) {
            u = anc[u][i];
        }
    }
    if (u == v) {
        return u;
    }
    for (int i = LOG-1; i >= 0; --i) {
        if (anc[u][i] != anc[v][i]) {
            u = anc[u][i];
            v = anc[v][i];
        }
    }
    return anc[u][0];
}

bool isSimpleIntersect(int x, int y, int u, int v) { // x is anc y, u is anc v
    if (u == v || x == y) return false;
    if (!isAnc(x, u) && !isAnc(u, x)) {
        return false;
    }
    if (isAnc(x, u)) {
        swap(x, u);
        swap(y, v);
    }
    // u is ancestor of x
    return isStrictAnc(x, v) && lca(y, v) != x;
}

bool isIntersect(int x, int y, int u, int v) {
    int luv = lca(u, v);
    int lxy = lca(x, y);
    return isSimpleIntersect(luv, u, lxy, x) 
        || isSimpleIntersect(luv, u, lxy, y)
        || isSimpleIntersect(luv, v, lxy, x)
        || isSimpleIntersect(luv, v, lxy, y);
}

int query(int e1, int u1, int v1, int e2, int u2, int v2) {
    int x1, y1, x2, y2;
    x1 = edges[e1].x; y1 = edges[e1].y;
    x2 = edges[e2].x; y2 = edges[e2].y;
    if (isAnc(x1, y1)) swap(x1, y1);
    if (isAnc(x2, y2)) swap(x2, y2);
    int pu1, pv1, pu2, pv2;
    pu1 = pv1 = pu2 = pv2 = 1;
    updateAnc(pu1, u1, x1, x2);
    updateAnc(pv1, v1, x1, x2);
    updateAnc(pu2, u2, x1, x2);
    updateAnc(pv2, v2, x1, x2);
    if (pu1 > pv1) swap(pu1, pv1);
    if (pu2 > pv2) swap(pu2, pv2);
    if (pu2 != pv2) {
        swap(pu1, pu2);
        swap(pv1, pv2);
        swap(u1, u2);
        swap(v1, v2);
    }
    if (pu1 != pv1) {
        if (pu2 == pv2) return 1;
        else return pu1 == pu2 && pv1 == pv2;
    } else {
        if (pu1 != pu2) {
            return 2;
        } else { // pu1 == pu2
            return isIntersect(u1, v1, u2, v2) ? 3 : 2;   
        }
    }
    assert(false);
    return 0;
}

int getRandom(int n) {
    cur = (1LL * mul * cur + add) % MOD;
    return 1 + cur % n;
}
int gspvhcute(void) {
    int res = 0;
    for (int i = 1; i <= q; i++) {
        int e1 = getRandom(numNode - 1);
        int u1 = getRandom(numNode); int v1 = getRandom(numNode);
        int e2 = getRandom(numNode - 1);
        int u2 = getRandom(numNode); int v2 = getRandom(numNode);
        int tmp = e1 == e2 || u1 == v1 || u2 == v2 
                ? MOD - 1 : query(e1, u1, v1, e2, u2, v2);
        res = (227LL * res + tmp) % MOD;
    }
    return res;
}

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    freopen("chutrinh.inp", "r", stdin);
    freopen("chutrinh.out", "w", stdout);

    int t; cin >> t;
    cin >> numNode;
    for (int i = 1; i < numNode; ++i) {
        cin >> edges[i].x >> edges[i].y;
        adj[edges[i].x].push_back(i);
        adj[edges[i].y].push_back(i);
    }
    cin >> q >> cur >> mul >> add;

    depth[0] = -1;
    dfs(1, 0);
    cout << gspvhcute();
    return 0;
}

Subtask 6

Tutorial

Sử dụng Euler tour và RMQ để tìm \(\texttt{lca}(u,v)\) trong \(O(1)\) và làm y hệt subtask 5.

Độ phức tạp: \(O(n \cdot \log(n) + q)\)

Solution
C++
#include <bits/stdc++.h>

using namespace std;

struct Edge {
    int x, y;
    Edge() {
        x = y = 0;
    }
    Edge(int _x, int _y) {
        x = _x; y = _y;
    }
};

const int MOD = (int)1e9 + 19972207;
int q, mul, add;
int cur;

const int N = 15e4+7;
const int LOG = 20;
int numNode;
Edge edges[N];
vector<int> adj[N];
int in[N], out[N], tim;
int siz, idx[N];
int depth[N], rmq[LOG][N*2];

void dfs(int u, int p) {
    in[u] = ++tim;
    rmq[0][++siz] = u;
    idx[u] = siz;
    for (int e : adj[u]) {
        int v = u ^ edges[e].x ^ edges[e].y;
        if (v!=p) {
            depth[v] = depth[u] + 1;
            dfs(v, u);
            rmq[0][++siz] = u;
        }
    }
    out[u] = tim;
}

void buildRMQ() {
    for (int j = 1; j < LOG; ++j) {
        for (int i = 1; i + (1<<j) - 1 <= siz; ++i) {
            rmq[j][i] = depth[rmq[j-1][i]] < depth[rmq[j-1][i+(1<<j-1)]] ? rmq[j-1][i] : rmq[j-1][i+(1<<j-1)];
        }
    }
}

inline bool isAnc(int u, int v) {
    return in[u] <= in[v] && in[v] <= out[u];
}

inline bool isStrictAnc(int u, int v) {
    return u != v && isAnc(u, v);
}

void updateAnc(int &a, int x, int u, int v) {
    if (isAnc(u, x) && isAnc(a, u)) a = u;
    if (isAnc(v, x) && isAnc(a, v)) a = v;
}

int lca(int u, int v) {
    if (idx[u] > idx[v]) {
        swap(u, v);
    }
    int k = 31 - __builtin_clz(idx[v] - idx[u] + 1);
    return depth[rmq[k][idx[u]]] < depth[rmq[k][idx[v]-(1<<k)+1]] ? rmq[k][idx[u]] : rmq[k][idx[v]-(1<<k)+1];
}

bool isSimpleIntersect(int x, int y, int u, int v) { // x is anc y, u is anc v
    if (u == v || x == y) return false;
    if (!isAnc(x, u) && !isAnc(u, x)) {
        return false;
    }
    if (isAnc(x, u)) {
        swap(x, u);
        swap(y, v);
    }
    // u is ancestor of x
    return isStrictAnc(x, v) && lca(y, v) != x;
}

bool isIntersect(int x, int y, int u, int v) {
    int luv = lca(u, v);
    int lxy = lca(x, y);
    return isSimpleIntersect(luv, u, lxy, x) 
        || isSimpleIntersect(luv, u, lxy, y)
        || isSimpleIntersect(luv, v, lxy, x)
        || isSimpleIntersect(luv, v, lxy, y);
}

int query(int e1, int u1, int v1, int e2, int u2, int v2) {
    int x1, y1, x2, y2;
    x1 = edges[e1].x; y1 = edges[e1].y;
    x2 = edges[e2].x; y2 = edges[e2].y;
    if (isAnc(x1, y1)) swap(x1, y1);
    if (isAnc(x2, y2)) swap(x2, y2);
    int pu1, pv1, pu2, pv2;
    pu1 = pv1 = pu2 = pv2 = 1;
    updateAnc(pu1, u1, x1, x2);
    updateAnc(pv1, v1, x1, x2);
    updateAnc(pu2, u2, x1, x2);
    updateAnc(pv2, v2, x1, x2);
    if (pu1 > pv1) swap(pu1, pv1);
    if (pu2 > pv2) swap(pu2, pv2);
    if (pu2 != pv2) {
        swap(pu1, pu2);
        swap(pv1, pv2);
        swap(u1, u2);
        swap(v1, v2);
    }
    if (pu1 != pv1) {
        if (pu2 == pv2) return 1;
        else return pu1 == pu2 && pv1 == pv2;
    } else {
        if (pu1 != pu2) {
            return 2;
        } else { // pu1 == pu2
            return isIntersect(u1, v1, u2, v2) ? 3 : 2;   
        }
    }
    assert(false);
    return 0;
}

int getRandom(int n) {
    cur = (1LL * mul * cur + add) % MOD;
    return 1 + cur % n;
}
int gspvhcute(void) {
    int res = 0;
    for (int i = 1; i <= q; i++) {
        int e1 = getRandom(numNode - 1);
        int u1 = getRandom(numNode); int v1 = getRandom(numNode);
        int e2 = getRandom(numNode - 1);
        int u2 = getRandom(numNode); int v2 = getRandom(numNode);
        int tmp = e1 == e2 || u1 == v1 || u2 == v2 
                ? MOD - 1 : query(e1, u1, v1, e2, u2, v2);
        res = (227LL * res + tmp) % MOD;
    }
    return res;
}

int main() {
    cin.tie(nullptr)->sync_with_stdio(false);
    freopen("chutrinh.inp", "r", stdin);
    freopen("chutrinh.out", "w", stdout);

    int t; cin >> t;
    cin >> numNode;
    for (int i = 1; i < numNode; ++i) {
        cin >> edges[i].x >> edges[i].y;
        adj[edges[i].x].push_back(i);
        adj[edges[i].y].push_back(i);
    }
    cin >> q >> cur >> mul >> add;

    depth[0] = -1;
    siz = 0;
    tim = 0;
    dfs(1, 0);
    buildRMQ();
    cout << gspvhcute();
    return 0;
}


Comments

There are no comments at the moment.