link。
好题啊。
首先有一个类 kruskal 暴力,就是对于每一个询问,把所有边按权值大小排降序,第一个加进去成为奇环的边就是答案。注意我们不需要关注偶环长成什么样子,所以我们实际上维护的是一棵生成树。这个可以用并查集维护结点到根的边的数量来实现。
因此我们需要关注的边只有 \(O(n)\) 条了(生成树),那么维护一个区间的需要关注的边的边集,具体而言就是树边和奇环上的边。考虑用线段树,合并的时候用归并即可。
int n, m, q, fa[2100], dst[2100], ms, mh; int u[1000100], v[1000100], w[1000100]; vi<bsi<int>> md; bool flag; int ldr(int x) { if (x != fa[x]) { int u = ldr(fa[x]); dst[x] ^= dst[fa[x]]; return fa[x] = u; } return x; } int mrg(int x, int y) { int u = ldr(x), v = ldr(y); if (u == v) { if (dst[x] == dst[y]) return 2; return 0; } fa[v] = u; dst[v] = dst[x]^dst[y]^1; return 1; } bsi<int> mrg(const bsi<int>& lhs, const bsi<int>& rhs) { bsi<int> qwq, res; merge(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), back_inserter(qwq), [&](int x, int y) { return w[x] > w[y]; }); for (auto i : qwq) { fa[u[i]] = u[i], fa[v[i]] = v[i]; dst[u[i]] = dst[v[i]] = 0; } for (auto i : qwq) { int tmp = mrg(u[i], v[i]); if (tmp) res += i; if (tmp == 2) { flag = 1; break; } } return res; } void upd(int now) { md[now] = mrg(md[now*2], md[now*2+1]); } int qry(int l, int r) { bsi<int> res; flag = 0; for (l += ms-1, r += ms; l < r; l >>= 1, r >>= 1) { if (l&1) res = mrg(res, md[l++]); if (r&1) res = mrg(md[--r], res); } if (flag) return w[res.back()]; return -1; } signed main() { ios::sync_with_stdio(0); cin.tie(0); cin >> n >> m >> q; mh = ceil(log2(m)), ms = 1<<mh; md = vi<bsi<int>>(ms*2, bsi<int>()); for (int i=1; i<=m; ++i) { cin >> u[i] >> v[i] >> w[i]; } for (int i=1; i<=m; ++i) { md[i+ms-1] = bsi<int>({i}); } for (int i=ms-1; i>=1; --i) { upd(i); } for (int l,r; q--;) { cin >> l >> r; cout << qry(l, r) << "\n"; } }