【杭电多校2020】Total Eclipse【贪心】【并查集】

题意: n n n个点 m m m条边的无向图,每个点有一个正点权,每次选择一个连通子图,将里面的权值都减 1 1 1。求所有点权为 0 0 0的最小步数。

T ≤ 10 , n ≤ 1 0 5 , m ≤ 2 × 1 0 5 T\leq 10,n\leq 10^5,m\leq2\times10^5 T10,n105,m2×105

考虑一个贪心:每次一定选择一个极大的连通块。

感性理解很容易,还是证明一下:

假设一个极大连通块 S S S,我偏不选,选择它的子连通块来覆盖整个 S S S,答案严格更优。考虑两个连在一起的连通块 T 1 , T 2 T_1,T_2 T1,T2,选择 T 1 ∪ T 2 , T 1 ∩ T 2 T_1\cup T_2,T_1\cap T_2 T1T2,T1T2一定不比选 T 1 , T 2 T_1,T_2 T1,T2劣。因为选择的连通块覆盖了整个 S S S,所以可以一步步合并出 S S S(即任选一个与当前集合相邻的点,将覆盖它的集合与当前集合合并),答案不会更劣,矛盾。

对于一个连通块来说,一定是点按照权值从小到大被删。把操作顺序倒过来,就是把大的结点减小成和小的结点相同,然后一起删掉。

形式化地讲,就是把权值从大到小排序依次加入,并把全场的权值都减到当前权值。用并查集维护连通块个数即可。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <vector>
#define MAXN 100005
using namespace std;
typedef long long ll;
vector<int> e[MAXN];
int fa[MAXN];
inline int find(const int& x){return fa[x]==x? x:fa[x]=find(fa[x]);}
int a[MAXN],p[MAXN],vis[MAXN];
inline bool cmp(const int& x,const int& y){return a[x]>a[y];}
int main()
{
	int T;
	scanf("%d",&T);
	while (T--)
	{
		int n,m;
		scanf("%d%d",&n,&m);
		for (int i=1;i<=n;i++) e[i].clear(),fa[i]=p[i]=i,vis[i]=0,scanf("%d",&a[i]);
		for (int i=1;i<=m;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			e[u].push_back(v),e[v].push_back(u);
		}
		sort(p+1,p+n+1,cmp);
		int cur=1;
		ll ans=0;
		vis[p[1]]=1;
		for (int i=2;i<=n;i++)
		{
			ans+=(ll)cur*(a[p[i-1]]-a[p[i]]);
			++cur;
			for (vector<int>::iterator it=e[p[i]].begin();it!=e[p[i]].end();++it)
			{
				int u=p[i],v=*it;
				if (!vis[v]) continue;
				u=find(u),v=find(v);
				if (u!=v) fa[u]=v,--cur;
			}
			vis[p[i]]=1;
		}
		ans+=(ll)cur*a[p[n]];
		printf("%lld\n",ans);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现城市燃气管道铺的问题,可以考虑使用Kruskal算法,该算法可以得到最小生成树,即最小的管道铺方案。 并查集是Kruskal算法的一个重要组成部分,用于检查新加入的边是否形成环路。下面是C语言实现Kruskal算法的示例代码,其中用到了并查集的数据结构。 ```c #include <stdio.h> #include <stdlib.h> #define MAX_VERTEX_NUM 20 // 定义边的结构体 typedef struct { int u; // 起点 int v; // 终点 int w; // 权值 } Edge; // 定义并查集结构体 typedef struct { int parent[MAX_VERTEX_NUM]; int rank[MAX_VERTEX_NUM]; } UnionFind; // 初始化并查集 void makeSet(UnionFind *uf, int n) { for (int i = 0; i < n; i++) { uf->parent[i] = i; uf->rank[i] = 0; } } // 查找根节点 int findRoot(UnionFind *uf, int x) { if (uf->parent[x] == x) { return x; } else { int root = findRoot(uf, uf->parent[x]); uf->parent[x] = root; return root; } } // 合并两个集合 void unionSet(UnionFind *uf, int x, int y) { int rootX = findRoot(uf, x); int rootY = findRoot(uf, y); if (rootX != rootY) { if (uf->rank[rootX] > uf->rank[rootY]) { uf->parent[rootY] = rootX; } else if (uf->rank[rootX] < uf->rank[rootY]) { uf->parent[rootX] = rootY; } else { uf->parent[rootY] = rootX; uf->rank[rootX]++; } } } // 比较函数,用于排序 int cmp(const void *a, const void *b) { return ((Edge *)a)->w - ((Edge *)b)->w; } // Kruskal算法 void kruskal(Edge edges[], int n, int m) { UnionFind uf; makeSet(&uf, n); qsort(edges, m, sizeof(Edge), cmp); int count = 0; int sum = 0; for (int i = 0; i < m; i++) { int rootU = findRoot(&uf, edges[i].u); int rootV = findRoot(&uf, edges[i].v); if (rootU != rootV) { unionSet(&uf, rootU, rootV); printf("%d -> %d : %d\n", edges[i].u, edges[i].v, edges[i].w); sum += edges[i].w; count++; } if (count == n - 1) { break; } } printf("Total cost: %d\n", sum); } // 测试 int main() { int n = 6, m = 10; Edge edges[] = { {0, 1, 6}, {0, 2, 1}, {0, 3, 5}, {1, 2, 5}, {1, 4, 3}, {2, 3, 5}, {2, 4, 6}, {2, 5, 4}, {3, 5, 2}, {4, 5, 6}, }; kruskal(edges, n, m); return 0; } ``` 以上代码中,我们将边按权值从小到大排序,然后遍历每条边,如果该边的起点和终点不在同一个集合中,则将它们合并,并输出该边的信息及权值。最后输出总的铺成本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值