线段树和离线算法的结合。假如你需要维护一些信息,这些信息会在某一个时间段内出现,要求在离线的前提下回答某一个时刻的信息并,则可以考虑使用线段树分治的技巧。

回顾线段树内容,假如需要维护一个时间轴上的操作,对这个时间轴建立线段树,把在某一时间段生效的操作挂在线段树节点上,在到达当前节点时,添加该节点上保存的操作;在离开该节点时,对于图的连通性问题通常使用可撤销并查集,对于其他特殊问题,如果需要维护的数据结构空间复杂度很小,也可以直接保存操作前的状态,离开该节点时再复制回原来的状态。询问操作一般存储在线段树叶节点上。

例题

  1. loj121 模板题

    维护一张图,动态添加一些边,删除一些已经存在的边,查询两个点是否连通

    将操作标记上生效时间,用线段树维护时间轴

    维护一个可撤销并查集

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    std::map<pii, std::vector<int>> mm;
    std::vector<std::tuple<int, int, int>> q;
    for (int i = 0; i < m; i++) {
    int op, x, y;
    cin >> op >> x >> y;
    if (y < x) {
    std::swap(x, y);
    }

    if (op == 0 || op == 1) {
    mm[{x, y}].push_back(i);
    } else {
    q.emplace_back(x, y, i);
    }
    }
    std::vector<std::vector<std::tuple<int, int, bool>>> seg(m << 2);

    auto modify = [&](auto &&self, int p, int l, int r, int x, int y,
    std::tuple<int, int, bool> v) -> void {
    if (l >= x && r <= y) {
    seg[p].emplace_back(v);
    return;
    }
    int m = (l + r) / 2;
    if (x <= m) {
    self(self, p << 1, l, m, x, y, v);
    }
    if (y >= m + 1) {
    self(self, p << 1 | 1, m + 1, r, x, y, v);
    }
    };

    DSU dsu(n + 1);
    auto dfs = [&](auto &&self, int p, int l, int r) -> void {
    int cnt = 0;
    for (auto [x, y, isq] : seg[p]) {
    if (isq) continue;
    cnt += dsu.merge(x, y);
    }

    if (l == r) {
    for (auto [x, y, isq] : seg[p]) {
    if (!isq) continue;
    if (dsu.same(x, y)) {
    cout << "Y\n";
    } else {
    cout << "N\n";
    }
    }
    while (cnt--) {
    dsu.revert();
    }
    return;
    }

    int m = (l + r) / 2;
    self(self, p << 1, l, m);
    self(self, p << 1 | 1, m + 1, r);

    while (cnt--) {
    dsu.revert();
    }
    };

    for (auto &[xy, vec] : mm) {
    auto [x, y] = xy;
    if (vec.size() & 1) {
    vec.push_back(m);
    }

    for (int i = 0; i < vec.size(); i += 2) {
    modify(modify, 1, 0, m - 1, vec[i], vec[i + 1], {x, y, false});
    }
    }
    for (auto [x, y, t] : q) {
    modify(modify, 1, 0, m - 1, t, t, {x, y, true});
    }
    dfs(dfs, 1, 0, m - 1);
    return 0;
    }
  2. P5787

    维护一张图是否是二分图

    维护一个可撤销并查集,用扩展域并查集来判断是否是二分图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n, m, k;
    cin >> n >> m >> k;

    std::vector<std::vector<pii>> seg((k + 1) << 2);

    auto modify = [&](auto && self, int p, int l, int r, int x, int y, pii v) -> void {
    if (l >= x && r <= y) {
    seg[p].emplace_back(v);
    return;
    }

    int m = (l + r) / 2;
    if (x <= m) {
    self(self, p << 1, l, m, x, y, v);
    }
    if (y >= m + 1) {
    self(self, p << 1 | 1, m + 1, r, x, y, v);
    }
    };

    for (int i = 0; i < m; i++) {
    int x, y, l, r;
    cin >> x >> y >> l >> r;
    l++;
    x--;
    y--;
    modify(modify, 1, 0, k, l, r, {x, y});
    }

    DSU dsu(n * 2);
    auto dfs = [&](auto && self, int p, int l, int r) -> void {
    int cnt = 0;
    bool check = true;
    for (auto [x, y] : seg[p]) {
    if (dsu.same(x, y)) {
    check = false;
    break;
    } else {
    cnt += dsu.merge(x, y + n);
    cnt += dsu.merge(x + n, y);
    }
    }
    if (l == r) {
    if (l > 0) {
    if (check) {
    cout << "Yes\n";
    } else {
    cout << "No\n";
    }
    }

    while (cnt--) {
    dsu.revert();
    }
    return;
    }

    if (!check) {
    for (int i = l; i <= r; i++) {
    cout << "No\n";
    }
    while (cnt--) {
    dsu.revert();
    }
    return;
    }

    int m = (l + r) / 2;
    self(self, p << 1, l, m);
    self(self, p << 1 | 1, m + 1, r);

    while (cnt--) {
    dsu.revert();
    }

    };

    dfs(dfs, 1, 0, k);
    return 0;
    }
  3. P5631

    找出一张图的最小 mex 树

    按照边权值轴建立线段树,如果一条边的权值为 w,那么就设置它的生效时间为 $[0,w-1],[w+1,w_{max}]$,依次检查每个 $w$ 时刻能否让图连通

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    int main() {
    // std::ios::sync_with_stdio(false);
    // std::cin.tie(nullptr);

    int n, m;
    // cin >> n >> m;
    read(n);
    read(m);

    int W = 100'000 + 1;
    std::vector<std::vector<pii>> seg((W + 1) << 2);

    auto modify = [&](auto &&self, int p, int l, int r, int x, int y,
    pii v) -> void {
    if (l >= x && r <= y) {
    seg[p].emplace_back(v);
    return;
    }

    int m = (l + r) / 2;
    if (x <= m) {
    self(self, p << 1, l, m, x, y, v);
    }
    if (y >= m + 1) {
    self(self, p << 1 | 1, m + 1, r, x, y, v);
    }
    };

    for (int i = 0; i < m; i++) {
    int u, v, w;

    read(u);
    read(v);
    read(w);
    if (u == v) continue;
    u--;
    v--;

    if (w - 1 >= 0) {
    modify(modify, 1, 0, W, 0, w - 1, {u, v});
    }
    modify(modify, 1, 0, W, w + 1, W, {u, v});
    }

    DSU dsu(n);
    int cnt = n;
    bool flag = false;

    auto dfs = [&](auto self, int p, int l, int r) -> void {
    if (flag) return;

    int t = 0;
    for (auto [x, y] : seg[p]) {
    if (dsu.same(x, y)) continue;
    dsu.merge(x, y);
    t++;
    cnt--;
    }

    if (l == r) {
    if (cnt == 1) {
    print(l, '\n');
    flag = true;
    return;
    }

    cnt += t;
    while (t--) {
    dsu.revert();
    }
    return;
    }

    int m = (l + r) / 2;
    self(self, p << 1, l, m);
    self(self, p << 1 | 1, m + 1, r);

    cnt += t;
    while (t--) {
    dsu.revert();
    }
    };

    dfs(dfs, 1, 0, W);

    return 0;
    }
  4. CF1681F

    给定一棵树,树边设置权值,$f(u,v)$ 为 $u,v$ 两点间简单路径上边权只出现一次的边权个数,同样按照边权值建立线段树,统计每条边的贡献。如果当前来到线段树叶节点 $w$,检查 $w$ 权值的所有边,由于原图是一棵树,能保证枚举当前边时,边左右两个端点的连通块一定不同,且只有当前边能让它们连通,$siz[u]\times siz[v]$ 即为当前边的贡献。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n;
    cin >> n;

    std::vector<std::vector<pii>> seg((n + 1) << 2);

    auto modify = [&](auto && self, int p, int l, int r, int x, int y, pii v) -> void {
    if (l >= x && r <= y) {
    seg[p].emplace_back(v);
    return;
    }
    int m = (l + r) / 2;
    if (x <= m) {
    self(self, p << 1, l, m, x, y, v);
    }
    if (y >= m + 1) {
    self(self, p << 1 | 1, m + 1, r, x, y, v);
    }
    };

    std::vector<std::vector<pii>> e(n + 1);
    for (int i = 0; i < n - 1; i++) {
    int u, v, x;
    cin >> u >> v >> x;
    u--;
    v--;
    e[x].emplace_back(u, v);

    if (x > 1) {
    modify(modify, 1, 1, n, 1, x - 1, {u, v});
    }
    if (x < n) {
    modify(modify, 1, 1, n, x + 1, n, {u, v});
    }
    }

    DSU dsu(n);
    i64 ans = 0;
    auto dfs = [&](auto && self, int p, int l, int r) -> void {
    int t = 0;
    for (auto [x, y] : seg[p]) {
    t += dsu.merge(x, y);
    }

    if (l == r) {
    for (auto [x, y] : e[l]) {
    ans += 1LL * dsu.size(x) * dsu.size(y);
    }

    while (t--) {
    dsu.revert();
    }
    return;
    }

    int m = (l + r) / 2;
    self(self, p << 1, l, m);
    self(self, p << 1 | 1, m + 1, r);

    while (t--) {
    dsu.revert();
    }
    };

    dfs(dfs, 1, 1, n);

    cout << ans << "\n";
    return 0;
    }
  5. P4219

    思路同上一题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    std::map<pii, std::vector<int>> mm;

    std::vector<bool> haveq(m);
    for (int i = 0; i < m; i++) {
    char op;
    int u, v;
    cin >> op >> u >> v;
    u--;
    v--;
    if (u > v) {
    std::swap(u, v);
    }

    mm[{u, v}].push_back(i);
    if (op == 'Q') {
    haveq[i] = true;
    }
    }
    std::vector<std::vector<std::tuple<int, int, bool>>> seg(m << 2);
    auto modify = [&](auto && self, int p, int l, int r, int x, int y, const std::tuple<int, int, bool> &v) -> void {
    if (l >= x && r <= y) {
    seg[p].emplace_back(v);
    return;
    }

    int m = (l + r) / 2;
    if (x <= m) {
    self(self, p << 1, l, m, x, y, v);
    }
    if (y >= m + 1) {
    self(self, p << 1 | 1, m + 1, r, x, y, v);
    }
    };

    for (auto &[edge, vec] : mm) {
    if (vec.size() == 1) {
    modify(modify, 1, 1, m, vec.front(), m, {edge.fi, edge.se, false});
    continue;
    }

    modify(modify, 1, 1, m, vec[0], vec[1] - 1, {edge.fi, edge.se, false});
    for (int i = 1; i < vec.size() - 1; i++) {
    if (vec[i] + 1 <= vec[i + 1] - 1) {
    modify(modify, 1, 1, m, vec[i] + 1, vec[i + 1] - 1, {edge.fi, edge.se, false});
    }
    }
    if (vec.back() < m) {
    modify(modify, 1, 1, m, vec.back() + 1, m, {edge.fi, edge.se, false});
    }

    for (int i = 1; i < vec.size(); i++) {
    modify(modify, 1, 1, m, vec[i], vec[i], {edge.fi, edge.se, true});
    }

    }

    DSU dsu(n);
    auto dfs = [&](auto && self, int p, int l, int r) -> void {
    int t = 0;
    for (auto &[x, y, isq] : seg[p]) {
    if (isq) continue;
    t += dsu.merge(x, y);
    }
    if (l == r) {
    for (auto &[x, y, isq] : seg[p]) {
    if (!isq) continue;
    cout << 1LL * dsu.size(x) * dsu.size(y) << "\n";
    break;
    }
    while (t--) {
    dsu.revert();
    }
    return;
    }

    int m = (l + r) / 2;
    self(self, p << 1, l, m);
    self(self, p << 1 | 1, m + 1, r);

    while (t--) {
    dsu.revert();
    }
    };

    dfs(dfs, 1, 1, m);
    return 0;
    }
  6. P5227

    模板题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    std::vector<pii> e;
    e.reserve(m);
    std::vector<std::vector<int>> opt(m);

    for (int i = 0; i < m; i++) {
    int u, v;
    cin >> u >> v;
    u--;
    v--;
    e.emplace_back(u, v);
    }

    int k;
    cin >> k;

    for (int i = 0; i < k; i++) {
    int t;
    cin >> t;
    while (t--) {
    int x;
    cin >> x;
    x--;
    opt[x].push_back(i + 1);
    }
    }

    std::vector<std::vector<int>> seg((k + 1) << 2);

    auto modify = [&](auto && self, int p, int l, int r, int x, int y, int v) -> void {
    if (l >= x && r <= y) {
    seg[p].emplace_back(v);
    return;
    }
    int m = (l + r) / 2;
    if (x <= m) {
    self(self, p << 1, l, m, x, y, v);
    }
    if (y >= m + 1) {
    self(self, p << 1 | 1, m + 1, r, x, y, v);
    }
    };

    for (int i = 0; i < m; i++) {
    int siz = opt[i].size();
    if (siz == 0) {
    modify(modify, 1, 0, k, 0, k, i);
    } else {
    modify(modify, 1, 0, k, 0, opt[i][0] - 1, i);
    for (int j = 0; j < siz - 1; j++) {
    if (opt[i][j] + 1 <= opt[i][j + 1] - 1) {
    modify(modify, 1, 0, k, opt[i][j] + 1, opt[i][j + 1] - 1, i);
    }
    }
    if (opt[i][siz - 1] + 1 <= k) {
    modify(modify, 1, 0, k, opt[i][siz - 1] + 1, k, i);
    }
    }
    }

    DSU dsu(n);
    auto dfs = [&](auto && self, int p, int l, int r) -> void {
    int t = 0;
    for (auto i : seg[p]) {
    auto [x, y] = e[i];
    t += dsu.merge(x, y);
    }

    if (l == r) {
    if (l > 0) {
    if (dsu.size(0) == n) {
    cout << "Connected\n";
    } else {
    cout << "Disconnected\n";
    }
    }
    while (t--) {
    dsu.revert();
    }
    return;
    }

    int m = (l + r) / 2;
    self(self, p << 1, l, m);
    self(self, p << 1 | 1, m + 1, r);

    while (t--) {
    dsu.revert();
    }
    };

    dfs(dfs, 1, 0, k);
    return 0;
    }
  7. CF576E

    初始给定一张图,边没有颜色,每次操作将边染上一种指定颜色,操作后,判断所有颜色中仅由该种颜色形成的图是否是二分图,如果是,保留操作,否则撤销这次操作。

    由于不合法则撤销,所以能保证每种颜色的边组成的图始终是二分图。如果将一条边由原来的 $old$ 色变为 $new$ 色,可以确保如果操作成功后,$old$ 色减少一条边,仍是二分图,所以我们只需要判断 $new$ 色的边加入当前一条新边后是否是二分图即可。

    该题目难点在于当前操作有可能被撤销,从而维持当前边之前的颜色。我们先按照经典的方式将所有操作加入时间轴线段树中,同时维护一个 $last$ 数组,表示 $last_i$ 为第 $i$ 条边上次的颜色,每次对边的染色操作从 $last_i$ 取出颜色,如果发现当前操作染色成功,则 $last_i=new$,否则不变。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n, m, k, q;
    cin >> n >> m >> k >> q;

    std::vector<pii> e;
    e.reserve(m);
    for (int i = 0; i < m; i++) {
    int u, v;
    cin >> u >> v;
    u--;
    v--;
    if (u > v) {
    std::swap(u, v);
    }
    e.emplace_back(u, v);
    }

    std::vector<int> last(m, -1);
    std::vector<std::vector<int>> seg(q << 2);

    std::vector<pii> query(q);
    std::vector<int> opt(m, -1);

    auto modify = [&](auto && self, int p, int l, int r, int x, int y, int v) -> void {
    if (l >= x && r <= y) {
    seg[p].push_back(v);
    return;
    }
    int m = (l + r) / 2;
    if (x <= m) {
    self(self, p << 1, l, m, x, y, v);
    }
    if (y >= m + 1) {
    self(self, p << 1 | 1, m + 1, r, x, y, v);
    }
    };

    for (int i = 0; i < q; i++) {
    int x, color;
    cin >> x >> color;
    x--;
    color--;
    query[i] = {x, color};

    if (~opt[x]) {
    modify(modify, 1, 0, q - 1, opt[x] + 1, i, x);
    }
    opt[x] = i;
    }

    for (int i = 0; i < m; i++) {
    if (opt[i] < q - 1 && opt[i] != -1) {
    modify(modify, 1, 0, q - 1, opt[i] + 1, q - 1, i);
    }
    }

    std::vector<DSU> dsu(k, DSU(n * 2));

    auto dfs = [&](auto && self, int p, int l, int r) -> void {
    std::vector<int> change;
    change.reserve(32);

    for (int edge : seg[p]) {
    auto [x, y] = e[edge];
    int color = last[edge];

    if (color == -1) continue;

    int t = 0;
    t += dsu[color].merge(x, y + n);
    t += dsu[color].merge(x + n, y);

    while (t--) {
    change.push_back(color);
    }
    }

    if (l == r) {
    auto [x, color] = query[l];
    auto [u, v] = e[x];
    int nw = color;

    auto &d = dsu[nw];
    if (d.same(u, v)) {
    cout << "NO\n";
    } else {
    cout << "YES\n";
    last[x] = nw;
    }

    for (int c : change) {
    dsu[c].revert();
    }
    return;
    }

    int m = (l + r) / 2;
    self(self, p << 1, l, m);
    self(self, p << 1 | 1, m + 1, r);

    for (int c : change) {
    dsu[c].revert();
    }
    };

    dfs(dfs, 1, 0, q - 1);
    return 0;
    }
  8. CF2104G

    给定一个内向基环森林,每次询问指定 k 种颜色,可以选择将每个点及其能到达的所有点染色,同时会修改一个点的出边,问对于每次询问,这个图最终会有多少种颜色不同的情况,答案对 3 取模。

    由于每次修改一条出边,形成的图仍旧是内向基环森林,分析不难得到假如整个图的 SCC 数量为 $c$,则对应答案为 $k^c \mod 3$,下面分析一下这个答案:

    • $k \equiv 0(\mod 3)\rightarrow k^c \equiv 0(\mod 3)$
    • $k\equiv 1 (\mod 3)\rightarrow k^c (\mod 3)$
    • $k\equiv 2(\mod 3)\rightarrow k^c\equiv 1(\mod 3)\text{ if k is even} , k^c\equiv 2(\mod 3)\text{ if k is odd}$

    统计强连通分量的数量很困难,因为通过并查集很难得出环的大小,但是可以很容易地得到环大小的奇偶性,因此只需要维护一个 $dis$ 带权并查集,就可以在合并时得到环的奇偶性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n, q;
    cin >> n >> q;

    std::vector<std::vector<pii>> opt(n);
    std::vector<int> query(q + 1);

    for (int i = 0; i < n; i++) {
    int g;
    cin >> g;
    g--;
    opt[i].push_back({g, 0});
    }

    for (int i = 1; i <= q; i++) {
    int x, y, k;
    cin >> x >> y >> k;
    query[i] = k;
    x--;
    y--;

    opt[x].push_back({y, i});
    }

    std::vector<std::vector<pii>> seg((q + 1) << 2);

    auto modify = [&](auto &&self, int p, int l, int r, int x, int y,
    const pii &v) -> void {
    if (l >= x && r <= y) {
    seg[p].push_back(v);
    return;
    }
    int m = (l + r) / 2;
    if (x <= m) {
    self(self, p << 1, l, m, x, y, v);
    }
    if (y >= m + 1) {
    self(self, p << 1 | 1, m + 1, r, x, y, v);
    }
    };
    for (int i = 0; i < n; i++) {
    for (int j = 0; j < opt[i].size() - 1; j++) {
    modify(modify, 1, 0, q, opt[i][j].se, opt[i][j + 1].se - 1,
    {i, opt[i][j].fi});
    }

    if (opt[i].size()) {
    modify(modify, 1, 0, q, opt[i].back().se, q, {i, opt[i].back().fi});
    }
    }

    bool odd = n & 1;
    DSU dsu(n);

    auto dfs = [&](auto &&self, int p, int l, int r) -> void {

    int t = 0;
    bool todd = odd;
    for (auto [x, y] : seg[p]) {
    if (dsu.same(x, y)) {
    int dx = dsu.getd(x);
    int dy = dsu.getd(y);
    if ((dx + dy) & 1) {
    odd ^= 1;
    }
    } else {
    dsu.merge(x, y);
    t++;
    }
    }
    if (l == r) {
    if (l > 0) {
    int k = query[l] % 3;
    if (k == 0 || k == 1) {
    cout << k << "\n";
    } else {
    cout << (odd ? 2 : 1) << "\n";
    }
    }
    while (t--) {
    dsu.revert();
    }
    odd = todd;
    return;
    }

    int m = (l + r) / 2;
    self(self, p << 1, l, m);
    self(self, p << 1 | 1, m + 1, r);

    while (t--) {
    dsu.revert();
    }
    odd = todd;
    };

    dfs(dfs, 1, 0, q);
    return 0;
    }