Solution -「CF 687D」Dividing Kingdom II

cirnovsky /

link。

好题啊。

首先有一个类 kruskal 暴力,就是对于每一个询问,把所有边按权值大小排降序,第一个加进去成为奇环的边就是答案。注意我们不需要关注偶环长成什么样子,所以我们实际上维护的是一棵生成树。这个可以用并查集维护结点到根的边的数量来实现。

因此我们需要关注的边只有 O(n)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";
    }
}