G. Yasya and the Mysterious Tree

G. Yasya and the Mysterious Tree

Yasya was walking in the forest and accidentally found a tree with n vertices. A tree is a connected undirected graph with no cycles.

Next to the tree, the girl found an ancient manuscript with m queries written on it. The queries can be of two types.

The first type of query is described by the integer y. The weight of each edge in the tree is replaced by the bitwise exclusive OR of the weight of that edge and the integer y.

The second type is described by the vertex v and the integer x. Yasya chooses a vertex u (1un, uv) and mentally draws a bidirectional edge of weight x from v to u in the tree.

Then Yasya finds a simple cycle in the resulting graph and calculates the bitwise exclusive OR of all the edges in it. She wants to choose a vertex u such that the calculated value is maximum. This calculated value will be the answer to the query. It can be shown that such a cycle exists and is unique under the given constraints (independent of the choice of u). If an edge between v and u already existed, a simple cycle is the path vuv.

Note that the second type of query is performed mentally, meaning the tree does not change in any way after it.

Help Yasya answer all the queries.

Input

The first line contains an integer t (1t104) — the number of test cases.

The descriptions of the test cases follow.

The first line of each test case contains two integers n, m (2n2105, 1m2105) — the number of vertices in the tree and the number of queries.

The next n1 lines of each test case contain three integers v, u, w (1v,un, 1w109) — the ends of some edge in the tree and its weight.

It is guaranteed that the given set of edges forms a tree.

The next m lines of each test case describe the queries:

  • ^ y (1y109) — parameter of the first type query;
  • ? v x (1vn, 1x109) — parameters of the second type query.

It is guaranteed that the sum of n over all test cases does not exceed 2105. The same is guaranteed for m.

Output

For each test case, output the answers to the queries of the second type.

Examples

input

2
3 7
1 2 1
3 1 8
^ 5
? 2 9
^ 1
? 1 10
^ 6
? 3 1
? 2 9
5 6
1 2 777
3 2 2812
4 1 16
5 3 1000000000
^ 4
? 3 123
? 5 1000000000
^ 1000000000
? 1 908070
? 2 1

output

13 15 11 10 
1000000127 2812 999756331 999999756 

input

3
8 4
8 6 3
6 3 4
2 5 4
7 6 2
7 1 10
4 1 4
5 1 2
^ 4
^ 7
? 7 8
? 4 10
5 6
3 1 4
2 3 9
4 3 6
5 2 10
? 5 7
^ 1
^ 8
? 4 10
? 1 9
? 3 6
4 2
2 1 4
4 3 5
2 3 4
^ 13
? 1 10

output

14 13 
13 8 11 11 
10 

 

解题思路

  对于询问操作,本质上就是找到一个点 u(uv) 使得 uv 两点构成路径的边权异或和,与 x 异或后的结果最大。为了求得任意一条路径的异或和,先通过 dfs 预处理出每个点 u 到根节点(固定节点 1 为根)路径上的异或和 su。那么任意两点 uv 构成路径的异或和就是 susv。假设 p=lca(u,v),路径 upv 的异或和就是 (susp)(svsp)=susv

  由于 svx 是固定的,所以就是找到一个 su,使得 su(svx) 的结果最大。这就是一个经典的问题,将每个 su 的二进制位从高到低存储到 Trie 中,然后从高位到低位贪心地找到使得异或 svx 结果最大的 su。又因为 uv,因此在查询前需要在 Trie 中删掉 sv,只需逻辑删除即可,即给 Trie 的每个节点开个计数器,在遍历 sv 的节点时只需对其计数器减 1 即可。查询完后还要再把 sv 插入到 Trie 中。

  再考虑修改操作对 su 的影响。如果 1u 的路径上有偶数条边,显然 su 的结果不会改变。否则 1u 的路径上有奇数条边,那么有 susux。对于每次修改操作,显然可以先把路径长度为奇数的 su 从 Trie 中删除,再插入 sux,也显然会超时。干脆根据路径长度的奇偶性维护两个 Trie,当查询偶数的 Trie 时,找的是异或 svx 的最大结果;查询奇数的 Trie 时,找的是异或 svxsum 的最大结果,其中 sum 是前面所有修改操作的异或和。

  AC 代码如下,时间复杂度为 O((n+m)logA)

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

typedef long long LL;

const int N = 2e5 + 5, M = N * 2, K = N * 30;

int h[N], e[M], wt[M], ne[M], idx;
int s[N], d[N];
int tr[2][K][2], cnt[K];

void add(int u, int v, int w) {
    e[idx] = v, wt[idx] = w, ne[idx] = h[u], h[u] = idx++;
}

void modify(int tr[][2], int x, int c){
    int p = 0;
    for (int i = 29; i >= 0; i--) {
        int t = x >> i & 1;
        if (!tr[p][t]) tr[p][t] = ++idx;
        p = tr[p][t];
        cnt[p] += c;
    }
}

void dfs(int u, int p) {
    modify(tr[d[u]], s[u], 1);
    for (int i = h[u]; i != -1; i = ne[i]) {
        int v = e[i];
        if (v == p) continue;
        s[v] = s[u] ^ wt[i];
        d[v] = d[u] ^ 1;
        dfs(v, u);
    }
}

int query(int tr[][2], int x) {
    int p = 0, ret = 0;
    for (int i = 29; i >= 0; i--) {
        int t = x >> i & 1;
        if (cnt[tr[p][t ^ 1]]) p = tr[p][t ^ 1], ret |= 1 << i;
        else p = tr[p][t];
    }
    return ret;
}

void solve() {
    int n, m;
    cin >> n >> m;
    idx = 0;
    memset(h, -1, n + 1 << 2);
    for (int i = 0; i < n - 1; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        add(u, v, w), add(v, u, w);
    }
    idx = 0;
    for (int i = 0; i <= 1; i++) {
        for (int j = 0; j < n * 30; j++) {
            tr[i][j][0] = tr[i][j][1] = 0;
        }
    }
    memset(cnt, 0, n * 30 + 1 << 2);
    dfs(1, 0);
    int sum = 0;
    while (m--) {
        char c;
        int x, y;
        cin >> c >> x;
        if (c == '^') {
            sum ^= x;
        }
        else {
            cin >> y;
            modify(tr[d[x]], s[x], -1);    // 先在相应的Trie中删除s[u]
            // 分别查询(u,v)路径长度为偶数、奇数的最大值
            cout << max(query(tr[d[x]], s[x] ^ y), query(tr[d[x] ^ 1], s[x] ^ y ^ sum)) << ' ';
            modify(tr[d[x]], s[x], 1); // 重新插入s[u]
        }
    }
    cout << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

 

参考资料

  Codeforces Round 950 (Div. 3) Editorial:https://codeforces.com/blog/entry/130135

posted @   onlyblues  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2023-06-05 C. No Prime Differences
2022-06-05 无线网络
Web Analytics
点击右上角即可分享
微信分享提示