P2573 [SCOI2012]滑雪-最小生成树

题目描述
a180285非常喜欢滑雪。他来到一座雪山,这里分布着M条供滑行的轨道和N个轨道之间的交点(同时也是景点),而且每个景点都有一编号 i ( 1 ≤ i ≤ N ) i(1 \le i \le N) i(1iN)和一高度H_i。a180285能从景点ii滑到景点jj当且仅当存在一条i和j之间的边,且i的高度不小于j。 与其他滑雪爱好者不同,a180285喜欢用最短的滑行路径去访问尽量多的景点。如果仅仅访问一条路径上的景点,他会觉得数量太少。于是a180285拿出了他随身携带的时间胶囊。这是一种很神奇的药物,吃下之后可以立即回到上个经过的景点(不用移动也不被认为是a180285 滑行的距离)。请注意,这种神奇的药物是可以连续食用的,即能够回到较长时间之前到过的景点(比如上上个经过的景点和上上上个经过的景点)。 现在,a180285站在11号景点望着山下的目标,心潮澎湃。他十分想知道在不考虑时间胶囊消耗的情况下,以最短滑行距离滑到尽量多的景点的方案(即满足经过景点数最大的前提下使得滑行总距离最小)。你能帮他求出最短距离和景点数吗?

思路
  1. 先按高度建图,再dfs或bfs搜出所有可以到达的点
  2. 把这些合法的边建一个新图,把他当做一个分层的图,同一高度就是一层,这样跑最小生成树就可以了

在这里插入图片描述

code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
inline int read(){
	char ch=' ';int f=1;int x=0;
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
const int N=1e5+100;
const int M=2e6+100;
int h[N];

struct node
{
	int v,nxt;
	int w;
}edge[M];
int head[N],cnt;

void add(int u,int v,int w)
{
	cnt++;
	edge[cnt].v=v;
	edge[cnt].w=w;
	edge[cnt].nxt=head[u];
	head[u]=cnt;
}
int cc;
struct data
{
	int h;int w;
	int a,b;
}d[M];
void push(int a,int b,int w)
{
	cc++;
	d[cc].a=a;d[cc].b=b;
	d[cc].w=w;
	d[cc].h=min(h[a],h[b]);
}

bool cmp(data x,data y)
{
	if(x.h==y.h) return x.w<y.w;
	return x.h>y.h;
}
bool vis[N];
int sum;

int q[N],he,ta;
void bfs()// 当然这里也可以用dfs来写,稍微快一点点
{
	he=ta=1;
	q[1]=1;vis[1]=true;sum++;
	while(he<=ta)
	{
		int u=q[he];
		for(int i=head[u];i;i=edge[i].nxt)
		{
			int v=edge[i].v;
			push(u,v,edge[i].w);
			if(!vis[v])
			{
				q[++ta]=v;
				sum++;
				vis[v]=true;
			}
		}
		he++;
	}
}
int f[N];
int getf(int p)
{
	if(f[p]==p)
	return p;
	
	return f[p]=getf(f[p]);
}
int main()
{
	int n,m;
	n=read();m=read();
	for(int i=1;i<=n;i++)
	h[i]=read();
	int u,v,w;
	for(int i=1;i<=m;i++)
	{
		u=read();v=read();w=read();
		if(h[u]>=h[v]) add(u,v,w);
		if(h[v]>=h[u]) add(v,u,w);
	}
	bfs();
	sort(d+1,d+1+cc,cmp);
	
	long long ans=0;
	for(int i=1;i<=n;i++) f[i]=i;
	for(int i=1;i<=cc;i++)
	{
		int f1=getf(d[i].a);
		int f2=getf(d[i].b);
		if(f1!=f2)
		{
			f[f1]=f2;
			ans+=d[i].w;
			n--;		
		}
		if(n==1) break;
	}
	cout<<sum<<' '<<ans<<endl;
	return 0;
}

//dfs版

void dfs(int u)
{
	if(vis[u]) return ;
	
	vis[u]=true;sum++;
	
	for(int i=head[u];i;i=edge[i].nxt)
	{
		int v=edge[i].v;
		int w=edge[i].w;
		push(u,v,w);
		dfs(v);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值