• Codeforces 1108F (MST Unification) (树上倍增 or 改进 kruksal)


    题意:给你一张n个节点和m条边的无向连通图, 你可以执行很多次操作,对某一条边的权值+1(对于每条边,可以不加,可以无限次加),问至少进行多少次操作,可以使这张图的最小生成树变得唯一,并且最小生成树的边权总和和原图的最小生成树一样。

    思路:容易发现, 我们没必要给一条边反复加权值,最多加一次就够了,因为加一次就已经可能改变最小生成树的总边权了。所以,这个题换一种说法,就是问这张图有多少条边,加入最小生成树之后删掉一条其它的边也是最小生成树。

    法1:我们首先可以把这颗最小生成树构造出来,然后暴力枚举每一条边,判断可不可以加到最小生成树中。假设这条边的端点是u, v,那么在树中就形成了一个环,为u  -> v -> LCA(u, v) -> u;我们只需要判断这条新加入的边是否比环中的其它边大就行了,不可能比环中的其它边的最大值小。因为如果这条边比环中边权最大的边小,那么删除边权最大的边就可以构成一颗更小的生成树,这不符合最小生成树的定义了,那么换句话说,我们构造出来的这棵树就不是最小生成树了。那么怎么找环上其它边的最大值呢?我们可以用倍增的思想,在倍增法求LCA的预处理时也可以把最值的预处理出来。查询最大值时套用LCA的框架更新最大值。

    代码:

    #include <bits/stdc++.h>
    #define INF 0x3f3f3f3f
    using namespace std;
    const int maxn = 200010;
    struct Edge{
    	int u, v, w;
    	bool operator < (const Edge& rhs) const {
    		return w < rhs.w;
    	}
    };
    Edge edge[maxn];
    int mx[maxn][20], f[maxn][20];
    int t;
    int head[maxn], Next[maxn * 2], edge1[maxn * 2], ver[maxn * 2], tot;
    int fa[maxn], d[maxn];
    queue<int> q;
    bool v[maxn];
    void add(int x, int y, int z) {
    	ver[++tot] = y;
    	edge1[tot] = z;
    	Next[tot] = head[x];
    	head[x] = tot;
    }
    
    int get(int x) {
    	if(x == fa[x]) return x;
    	return fa[x] = get(fa[x]);
    }
    
    void bfs() {
    	q.push(1);
    	d[1] = 1;
    	while(q.size()) {
    		int x = q.front();
    		q.pop();
    		for(int i = head[x]; i; i = Next[i]) {
    			int y = ver[i], z = edge1[i];
    			if(d[y]) continue;
    			d[y] = d[x] + 1;
    			f[y][0] = x;
    			mx[y][0] = z;
    			for(int j = 1; j <= t; j++) {
    				f[y][j] = f[f[y][j - 1]][j - 1];
    				mx[y][j] = max(mx[y][j - 1], mx[f[y][j - 1]][j - 1]);
    			}
    			q.push(y);
    		}
    	}
    }
    
    int query(int x, int y) {
    	if(d[x] > d[y]) swap(x, y);
    	int ans = -INF;
    	for(int i = t; i >= 0; i--) {
    		if(d[f[y][i]] >= d[x]) {
    			ans = max(ans, mx[y][i]);
    			y = f[y][i];
    		}
    	}
    	if(x == y) return ans;
    	for(int i = t; i >= 0; i--) {
    		if(f[x][i] != f[y][i]) {
    			ans = max(ans, mx[x][i]);
    			ans = max(ans, mx[y][i]);
    			x = f[x][i];
    			y = f[y][i];
    		}
    	}
    	return max(ans, max(mx[x][0], mx[y][0]));
    }
    int main() {
    	int n, m;
    //	freopen("in.txt", "r", stdin);
    	scanf("%d%d", &n, &m);
    	t = (int)(log(n) / log(2)) + 1;
    	for(int i = 1; i <= m; i++) {
    		scanf("%d%d%d",&edge[i].u, &edge[i].v, &edge[i].w);
    	}
    	sort(edge + 1, edge + 1 + m);
    	for(int i = 1; i <= n; i++) fa[i] = i;
    	for(int i = 1; i <= m; i++) {
    		int x = get(edge[i].u);
    		int y = get(edge[i].v);
    		if(x == y)continue;
    		fa[x] = y;
    		v[i] = 1;
    		add(edge[i].u, edge[i].v, edge[i].w);
    		add(edge[i].v, edge[i].u, edge[i].w);
    	}
    	bfs();
    	int ans = 0;
    	for(int i = 1; i <= m; i++) {
    		if(v[i])continue;
    		int tmp = query(edge[i].u, edge[i].v);
    		if(tmp == edge[i].w) ans++;
    	}
    	printf("%d
    ", ans);
    }
    

    法2:对于边权相同的边,我们可以把它们分成2类,一类是不可能成为最小生成树的边的边(之前有更小的边已经形成了一条链了),一类是可能的边,只是有一些边运气比较好,成为了当前最小生成树的边。我们就可以统计这些“运气不好”的边,运气不好“的边数目总和就是最终答案。具体过程:对于边权相同的边,我们先找出本来就不可能的边,在找到那些能成为最小生成树的“幸运边”,剩下的就是“运气不好”的边了。

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 200010;
    struct Edge{
    	int u,v,w;
    	bool operator < (const Edge& rhs) const {
    		return w < rhs.w;
    	}
    };
    Edge edge[maxn];
    int fa[maxn];
    int get(int x) {
    	if(x == fa[x]) return x;
    	return fa[x] = get(fa[x]);
    }
    
    bool merge(int x, int y) {
    	int tmp1 = get(x);
    	int tmp2 = get(y);
    	if(tmp1 == tmp2) return 0;
    	fa[tmp1] = tmp2;
    	return 1;
    }
    
    int main() {
    	int n, m;
    	scanf("%d%d", &n, &m);
    	for(int i = 1; i <= m; i++) {
    		scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].w);
    	}
    	sort(edge + 1, edge + 1 + m);
    	for(int i = 1; i <= n; i++) fa[i] = i;
    	int ans = 0, sum = 0;
    	for(int i = 1; i <= m; i++) {
    		int j = i;
    		while(edge[j + 1].w == edge[j].w) j++;
    		sum = j - i + 1;
    		for(int k = i; k <= j; k++) {
    			if(get(edge[k].u) == get(edge[k].v))
    				sum--;
    		}
    		for(int k = i; k <= j; k++) {
    			sum -= merge(edge[k].u, edge[k].v);
    		}
    		ans += sum;
    		i = j;
    	}
    	printf("%d
    ", ans);
    } 
    

      

  • 相关阅读:
    Docker学习(二): 镜像的使用与构建
    Docker学习(一): 基本概念
    SVN笔记
    标准Trie字典树学习二:Java实现方式之一
    标准Trie字典树学习一:原理解析
    SQL优化
    企业信息管理系统需求分析及要设计——程序的设计与实现①
    企业信息管理系统需求分析及要设计
    网络版ATM项目的实现——服务端
    网络版ATM项目的实现——客户端
  • 原文地址:https://www.cnblogs.com/pkgunboat/p/10321153.html
Copyright © 2020-2023  润新知