[SCOI2012]滑雪与时间胶囊

一、题目

NC20568

二、思路

这题好像kruskal和prim都能写
大概题意是从1号点出发,求能经过的最多点的最小生成树

因为不一定所有的点都能达到,所以先做一遍bfs把从1号点开始能达到的点标记上
然后对所有的边进行排序,按高度为第一关键字降序,边权第二关键字升序,这样就能保证在点最大的同时总边权最小

三、代码

#include<bits/stdc++.h>
using namespace std;
long long sum;
long long h[1000010];
int s[1000010];
bool vis[1000010];
int idx;
int head[1000010];
long long cnt;
long long n, m;
struct Edge{
	int u; int v;
	int w;
	int next;
	bool operator < (const Edge & b) const{
		if(h[v] == h[b.v]) return w < b.w;
		return h[v] > h[b.v];
	}
}edges[2000020];
void add(int a, int b, int c){
	idx ++;
	edges[idx].u = a;
	edges[idx].v = b;
	edges[idx].w = c;
	edges[idx].next = head[a];
	head[a] = idx;
}
void bfs(int start){
	memset(vis, 0, sizeof vis);
	queue<int> q;
	q.push(start);
	vis[start] = 1;
	cnt ++;
	while(q.size()){
		int u = q.front();
		q.pop();
		for(int i = head[u]; i != -1; i = edges[i].next){
			int v = edges[i].v;
			if(!vis[v]){
				vis[v] = 1;
				q.push(v);
				cnt ++;
			}
		}
	}
}
int find(int x){
	if(s[x] != x) s[x] = find(s[x]); //这里记得路径压缩,不然会超时
	return s[x];
}
void kruskal(){
	sum = 0;
	for(int i = 1; i <= n; i ++) s[i] = i;
	for(int i = 1; i <= idx; i ++){
		int u = edges[i].u, v = edges[i].v, w = edges[i].w;
		if(vis[u] && vis[v]){ //这两个点在同一个点集合中
			if(find(u) != find(v)){
				s[find(u)] = find(v);
				sum += w;
			}
		}
	}
}
int main(){
	cin >> n >> m;
	memset(head, -1, sizeof head);
	for(int i = 1; i <= n; i ++){
		cin >> h[i];
	}
	int a, b, c;
	for(int i = 1; i <= m; i ++){
		scanf("%d%d%d", &a, &b, &c);
		if(h[a] >= h[b]) add(a, b, c); //如果相等的话要连条双向的边,否则连一条从高到低的单项边
		if(h[b] >= h[a]) add(b, a, c);
	}
	bfs(1);
	sort(edges + 1, edges + 1 + idx);
	kruskal();
	cout << cnt << " " << sum << endl;
	return 0;
}
posted @ 2021-08-09 16:23  行舟C  阅读(38)  评论(0)    收藏  举报