• LOJ6807 「THUPC 2022 初赛」最小公倍树


    LOJ6807 「THUPC 2022 初赛」最小公倍树

    题目大意

    题目链接

    给定两个正整数 \(L, R​\)。考虑一张 \(R - L + 1​\) 个节点的无向图,节点编号分别为 \(L, L + 1, \dots, R​\),任意两个节点之间都有连边,边权为两点点权的最小公倍数(\(\mathrm{lcm}​\))。求这张图的最小生成树的边权和。

    数据范围:\(1\leq L\leq R\leq 10^6\)\(R - L \leq 10^5\)

    本题题解

    考虑用 Kruskal 算法求最小公倍数,也就是贪心,每次取出两个端点不在一个连通块的、边权最小的边。问题是,这张图里的边实在是太多了,所以我们要考虑,什么样的边有可能成为被选中的边。从最小公倍数的特殊设定入手。

    最小”公倍数,这个要求太严格了,有点烦人。不妨这样重建一张新图:对于每个正整数 \(k\),在所有点权为 \(k\) 的倍数的点里,两两连边,边权为点权之积除以 \(k\)。也就是说,我们现在只关心“公倍数”,而不关心“最小”的要求。所以,这张新图里会包含原图所有的边,同时多出一些边。不过,多出的边边权一定大于原有的边,所以不会对答案产生影响。接下来我们只需要在这张新图上求最小生成树。

    考虑点权为 \(k\) 的倍数的点所产生的边,哪些有可能被当前 Kruskal 的贪心选中呢?一定是点权最小的两个不在同一连通块里的点之间的边。具体来说,是大于等于 \(L\) 的第一个 \(k\) 的倍数,和最小的与它不在同一连通块里的 \(k\) 的倍数,之间的边。这是因为所有边边权都是点权之积除以 \(k\),在 \(k\) 固定的情况下,一定选点权之积最小的边。

    当然,上段讨论的是假设 Kruskal 已经在执行的过程中,我们已经知道哪些点在同一个连通块的情况。那么在所有过程开始之前,我们如何知道哪些边有可能被选中呢?仔细看上段的结论,我们发现一个惊人的性质:对于 \(k\)不管怎么选,选出来的边一定有一个端点是大于等于 \(L\) 的第一个 \(k\) 的倍数。也就是说,在开始时,对于每个 \(k\),我们只需要保留以这个点为端点的边就可以了,这样的边大约有 \(\frac{R - L + 1}{k} - 1\) 条,而其他的边可以全部扔掉,不会影响答案!设 \(n = R - L + 1\),那么我们需要的总边数是 \(\mathcal{O}\left(\sum_{k = 1}^{n} \frac{n}{k}\right) = \mathcal{O}(n\log n)\) 级别的(调和级数的结论)。

    至此,我们已经可以得到一个简单的做法:把这 \(\mathcal{O}(n\log n)\) 条边拿出来,排序,然后执行 Kruskal 算法。这样做所需的空间复杂度是 \(\mathcal{O}(n\log n)\) 的。还有一种更省空间的写法是,用 \(\texttt{std::priority_queue}\),每次弹出一条边(记下这条边对应的 \(k\)),如果两个端点在同一连通块内,就把右端点挪到该 \(k\) 对应的下一个右端点,得到一条新的边,加入队列。因为同一个 \(k\) 对应的边权是随着右端点的递增而递增的,所以它本质上就是给这 \(n​\) 个有序序列做归并排序。

    时间复杂度 \(\mathcal{O}(n\log^2 n)\),空间复杂度 \(\mathcal{O}(n)\)

    参考代码

    // problem: LOJ6807
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    
    template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
    template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
    
    const int MAXN = 1e5 + 1;
    
    int L, R;
    int f[MAXN + 5], g[MAXN + 5];
    
    int fa[MAXN + 5], sz[MAXN + 5];
    int get_fa(int u) {
    	return (fa[u] == u) ? u : (fa[u] = get_fa(fa[u]));
    }
    void unite(int u, int v) {
    	int uu = get_fa(u);
    	int vv = get_fa(v);
    	if (uu != vv) {
    		if (sz[uu] > sz[vv])
    			swap(uu, vv);
    		fa[uu] = vv;
    		sz[vv] += sz[uu];
    	}
    }
    
    int main() {
    	cin >> L >> R;
    	priority_queue<pair<ll, int> > que;
    	for (int i = 1; i <= R - L; ++i) {
    		f[i] = ((L - 1) / i + 1) * i; // >= L 的第一个 i 的倍数
    		if (f[i] + i <= R) {
    			g[i] = f[i] + i;
    			que.push(mk(-(ll)f[i] / i * g[i], i));
    		}
    	}
    	for (int i = 1; i <= R - L + 1; ++i) {
    		fa[i] = i;
    		sz[i] = 1;
    	}
    	ll ans = 0;
    	for (int i = 1; i <= R - L; ++i) {
    		pair<ll, int> t = que.top();
    		que.pop();
    		while (get_fa(f[t.se] - L + 1) == get_fa(g[t.se] - L + 1)) {
    			g[t.se] += t.se;
    			if (g[t.se] <= R) {
    				que.push(mk(-(ll)f[t.se] / t.se * g[t.se], t.se));
    			}
    			t = que.top();
    			que.pop();
    		}
    		ans -= t.fi; // 根据默认大根堆的特点,t.fi 是负数
    		unite(f[t.se] - L + 1, g[t.se] - L + 1);
    		g[t.se] += t.se;
    		if (g[t.se] <= R) {
    			que.push(mk(-(ll)f[t.se] / t.se * g[t.se], t.se));
    		}
    		// cerr << "add " << f[t.se] << " " << g[t.se] << endl;
    	}
    	cout << ans << endl;
    	return 0;
    }
    
  • 相关阅读:
    Resharper进阶一
    脚本(js)控制页面输入
    IE图标消失 HTML文件图标变为未知图标的解决方法
    memcache_engine + memcachedb = 高性能分布式内存数据库
    sql 求差值
    MSN、QQ的网页链接代码
    IFrame语法:IFrame实例应用集
    Memcache协议
    Windows下的Memcache安装
    文本框 价格 保留两位小数 讨论
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/LOJ6807.html
Copyright © 2020-2023  润新知