• APIO2020 交换城市


    我是真的不稳定的垃圾选手。

    对于一张图来说,两个人能满足题面关系等价于这张图不是链,很好证明,如果有度数 (> 2) 的点,让一个人跑到一个度数 (= 1) 的地方就可以了。

    如果离线就可以什么启发式合并 + 并查集做到一个 (log)。就是合并两个点 (x, y),动态维护每个联通块是不是链。

    • 连接两个已经在同一个联通块的点变成环就不是链了
    • (x, y) 之前所在联通块不是链总共也不是链了
    • (x, y) 其中有一个是不是端点总共也不是链了。
    • 如果 (x) 所在联通块大小 (> 1) 那么 (x) 就不是端点了,(y) 同理。

    然后这是强制在线的。。可持久化并查集,询问的时候要二分,每次 check 还是两个 (log),最后 (O(m log^2 n + q log ^ 3 n)),居然只会 TLE 五六个点。。。然后我也不会卡常,自闭了。

    #include <vector>
    #include <algorithm>
    #include <iostream>
    using namespace std;
    
    const int MAXN = 100005, MAXM = 200005;
    
    int n, m, arr[MAXM], tot;
    
    struct E{
    	int u, v, w;
    	bool operator < (const E &b) const {
    		return w < b.w;
    	}
    } e[MAXM];
    
    struct T{
    	int l, r, v;
    };
    
    struct Array{
    	int rt[MAXM], idx;
    	T t[MAXN * 22];
    	void build(int &p, int l, int r, int o) {
    		p = ++idx;
    		if (l == r) { t[p].v = o == -1 ? r : o; return ; }
    		int mid = (l + r) >> 1;
    		build(t[p].l, l, mid, o);
    		build(t[p].r, mid + 1, r, o);
    	}
    	void update(int &p, int q, int l, int r, int x, int o) {
    		t[p = ++idx] = t[q];
    		if (l == r) { t[p].v = o; return; }
    		int mid = (l + r) >> 1;
    		if (x <= mid) update(t[p].l, t[q].l, l, mid, x, o);
    		else update(t[p].r, t[q].r, mid + 1, r, x, o);
    	}
    	int query(int p, int l, int r, int x) {
    		if (l == r) return t[p].v;
    		int mid = (l + r) >> 1;
    		if (x <= mid) return query(t[p].l, l, mid, x);
    		else return query(t[p].r, mid + 1, r, x);
    	}
    	void inline set(int x, int k, int i) { update(rt[i], rt[i], 0, n - 1, x, k); }
    	int inline get(int x, int i) { return query(rt[i], 0, n - 1, x); }
    } p, f, d, sz;
    
    int find(int x, int i) {
    	int fa = f.get(x, i);
    	return x == fa ? x : find(fa, i);
    }
    
    void inline merge(int x, int y, int i) {
    	int a = find(x, i), b = find(y, i);
    	if (a == b) {
    		p.set(a, 1, i);
    	} else {
    		int sa = sz.get(a, i), sb = sz.get(b, i);
    		if (sa > sb) swap(sa, sb), swap(a, b), swap(x, y);
    		f.set(a, b, i); 
    		int dx = d.get(x, i), dy = d.get(y, i);
    		if (p.get(a, i) || !dx || !dy) p.set(b, 1, i);
    		if (dx && sa > 1) d.set(x, 0, i);
    		if (dy && sb > 1) d.set(y, 0, i);
    		sz.set(b, sa + sb, i);
    	}
    }
    
    void init(int N, int M,
              std::vector<int> U, std::vector<int> V, std::vector<int> W) {
    	n = N, m = M;
    	for (int i = 0; i < m; i++) {
    		arr[++tot] = W[i];
    		e[i] = (E) { U[i], V[i], W[i] };
    	}
    	sort(arr + 1, arr + 1 + tot);
    	tot = unique(arr + 1, arr + 1 + tot) - arr - 1;
    	sort(e, e + m);
    	f.build(f.rt[0], 0, n - 1, -1), sz.build(sz.rt[0], 0, n - 1, 1);
    	p.build(p.rt[0], 0, n - 1, 0), d.build(d.rt[0], 0, n - 1, 1);
    	for (int i = 1, j = 0; i <= tot; i++) {
    		p.rt[i] = p.rt[i - 1], f.rt[i] = f.rt[i - 1];
    		d.rt[i] = d.rt[i - 1], sz.rt[i] = sz.rt[i - 1];
    		while (j < m && e[j].w <= arr[i]) {
    			merge(e[j].u, e[j].v, i);
    			++j;
    		}
    	}
    }
    
    bool inline check(int X, int Y, int i) {
    	int a = find(X, i), b = find(Y, i);
    	return a == b && p.get(a, i);
    }
    
    int getMinimumFuelCapacity(int X, int Y) {
    	if (!check(X, Y, tot)) return -1;
    	int l = 1, r = tot;
    	while (l < r) {
    		int mid = (l + r) >> 1;
    		if (check(X, Y, mid)) r = mid;
    		else l = mid + 1;
    	}
    	return arr[r];
    }
    

    然后就去无耻的看题解,然后去学了一下 Kruscal 重构树

    我们不妨设计一个特殊的 Kruscal 重构树,使得对于 (x, y) 的询问,答案就是他们的 LCA ,即他们的 LCA 是合并过程中让他们从链变成不是链的那条关键边!

    那么,设计一种类似于普通 Kruscal 最小重构树的方式,只不过改动如下:

    • 对于合并还是链的联通块,先不要在重构树上连边。
    • 当一条链 (Rightarrow) 不是链的时候,把这条边的新点是链里所有点两两的关键边,所以把这个新点作为这条链上所有点的父亲就行了!这样的意义是这些点两两关键边就是这个边。

    发现这么构造仍然满足大根堆性质。

    用启发式合并存储当前联通块的所有点,每个原始点只会连一次,即连向他所在联通块转变的关键边新点,所以复杂度是 (O(n log n)) 的。

    正确性:在 LCA 这个子树中,所有边都是 (le w_{LCA}) 的,LCA 的子树形成的结构不是链,因为只有不是链才会连边,所以必然 LCA 的权值是可以的。然后证明 (ans ge w_{LCA})(ans < w_{LCA}) 显然是不行的,因为他们要用这条关键边。所以 (ans = w_{LCA})

    所以每次询问求个 LCA 就行了。

    时间复杂度 (O((n + q) log n))

    我代码偷了一下懒,即只在链的时候维护各种信息,已经不是链了,除了 (f) 数组剩下就不用维护。

    #include "swap.h"
    
    #include <algorithm>
    #include <vector>
    #include <iostream>
    
    using namespace std;
    
    const int N = 100005, M = 200005, L = 19;
    
    int n, m, f[N << 1], fa[N << 1][L], dep[N << 1], cnt, sz[N], a[N << 1];
    
    bool ch[N << 1], d[N];
    
    vector<int> g[N << 1];
    vector<int> c[N << 1];
    
    struct E {
        int u, v, w;
        bool operator<(const E &b) const { return w < b.w; }
    } e[M];
    
    int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
    
    void dfs(int u) {
        for (int i = 1; i < L && fa[u][i - 1]; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
        for (int i = 0; i < g[u].size(); i++) {
            int v = g[u][i];
            fa[v][0] = u, dep[v] = dep[u] + 1;
            dfs(v);
        }
    }
    
    int lca(int x, int y) {
        if (dep[x] < dep[y])
            swap(x, y);
        for (int i = L - 1; ~i; i--)
            if (dep[x] - (1 << i) >= dep[y])
                x = fa[x][i];
        if (x == y)
            return x;
        for (int i = L - 1; ~i; i--)
            if (fa[x][i] != fa[y][i])
                x = fa[x][i], y = fa[y][i];
        return fa[x][0];
    }
    
    void inline merge(int u, int v, int w) {
        int x = find(u), y = find(v);
        if (x == y) {
            if (ch[x]) {
                a[++cnt] = w;
                for (int i = 0; i < sz[x]; i++) g[cnt].push_back(c[x][i]);
                f[x] = cnt, ch[cnt] = false;
            }
        } else {
            if (sz[x] > sz[y])
                swap(x, y), swap(u, v);
            if (!ch[x] || !ch[y] || !d[u] || !d[v]) {
                a[++cnt] = w;
                if (ch[y])
                    for (int i = 0; i < sz[y]; i++) g[cnt].push_back(c[y][i]);
                else
                    g[cnt].push_back(y);
                if (ch[x])
                    for (int i = 0; i < sz[x]; i++) g[cnt].push_back(c[x][i]);
                else
                    g[cnt].push_back(x);
                f[x] = f[y] = cnt;
            } else {
                if (sz[x] > 1)
                    d[u] = false;
                if (sz[y] > 1)
                    d[v] = false;
                f[x] = y, sz[y] += sz[x];
                for (int i = 0; i < sz[x]; i++) c[y].push_back(c[x][i]);
            }
        }
    }
    
    void init(int N, int M, std::vector<int> U, std::vector<int> V, std::vector<int> W) {
        n = cnt = N, m = M;
        for (int i = 1; i <= m; i++) e[i] = (E){ U[i - 1] + 1, V[i - 1] + 1, W[i - 1] };
        sort(e + 1, e + 1 + m);
        for (int i = 1; i < 2 * n; i++) {
            f[i] = i;
            if (i <= n)
                sz[i] = 1, c[i].push_back(i), ch[i] = true, d[i] = true;
        }
        for (int i = 1; i <= m; i++) merge(e[i].u, e[i].v, e[i].w);
        if (cnt > n)
            dfs(cnt);
    }
    
    int getMinimumFuelCapacity(int X, int Y) {
        ++X, ++Y;
        if (ch[find(X)])
            return -1;
        return a[lca(X, Y)];
    }
    
  • 相关阅读:
    python与常用模块pandas,numpy,matplotlib等库学习笔记-2019.02.07更新
    C++异常处理相关用法及底层机制
    C++ regex库常用函数及实例
    leetcode-2-两数相加(链表)
    leetcode-1-两数之和(三种方法)
    中序遍历(递归+迭代)
    C++实现四则运算器(带括号)
    C++实现四则运算器(无括号)
    Visual Studio2019 基于WSL的Linux C++开发
    Visual Studio 2019 基于Linux平台的C++开发
  • 原文地址:https://www.cnblogs.com/dmoransky/p/13817859.html
Copyright © 2020-2023  润新知