【题解】 [SCOI2012]滑雪

目录

  • 题目
    • 题目描述
    • 输入格式
    • 输出格式
    • 输入输出样例
      • 输入 #1
      • 输出 #1复制
    • 说明/提示
      • 【数据范围】
  • 题解
    • 整理题目
    • 思路
      • 考虑顺序
      • 1.只能从高到低
      • 2.到的景点最多、总距离最短
    • 坑点
    • 最后就是代码

题目

题目描述

a180285 非常喜欢滑雪。他来到一座雪山,这里分布着 m m m 条供滑行的轨道和 n n n 个轨道之间的交点(同时也是景点),而且每个景点都有一编号 i   ( 1 ≤ i ≤ n ) i\space (1 \le i \le n) i (1in)和一高度 h i h_i hi

a180285 能从景点 i i i 滑到景点 j j j 当且仅当存在一条 i i i j j j 之间的边,且 i i i 的高度不小于 j j j。与其他滑雪爱好者不同,a180285 喜欢用最短的滑行路径去访问尽量多的景点。如果仅仅访问一条路径上的景点,他会觉得数量太少。

于是 a180285 拿出了他随身携带的时间胶囊。这是一种很神奇的药物,吃下之后可以立即回到上个经过的景点(不用移动也不被认为是 a180285 滑行的距离)。

请注意,这种神奇的药物是可以连续食用的,即能够回到较长时间之前到过的景点(比如上上个经过的景点和上上上个经过的景点)。 现在,a180285站在 1 1 1 号景点望着山下的目标,心潮澎湃。他十分想知道在不考虑时间胶囊消耗的情况下,以最短滑行距离滑到尽量多的景点的方案(即满足经过景点数最大的前提下使得滑行总距离最小)。你能帮他求出最短距离和景点数吗?

输入格式

输入的第一行是两个整数 n , m n,m n,m。 接下来一行有 n n n 个整数 h i h_i hi ,分别表示每个景点的高度。
接下来 m m m 行,表示各个景点之间轨道分布的情况。每行三个整数 u , v , k u,v,k u,v,k,表示编号为 u u u 的景点和编号为 v v v 的景点之间有一条长度为 k k k 的轨道。

输出格式

输出一行,表示 a180285 最多能到达多少个景点,以及此时最短的滑行距离总和。

输入输出样例

输入 #1

3 3 
3 2 1 
1 2 1 
2 3 1 
1 3 10

输出 #1复制

3 2

说明/提示

【数据范围】

对于 30 % 30\% 30% 的数据, 1 ≤ n ≤ 2000 1 \le n \le 2000 1n2000
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1n105
对于所有的数据,保证 1 ≤ m ≤ 1 0 6 1 \le m \le 10^6 1m106 , 1 ≤ h i ≤ 1 0 9 1 \le h_i \le 10^9 1hi109 1 ≤ k i ≤ 1 0 9 1 \le k_i \le 10^9 1ki109

题解

整理题目

可以发现,整个景区可以抽象为一张图(明显废话),景点的高度为点权,景点间的距离为边权,所以这个长的像坨shit的题面就被我们翻译成了一句话:在只能从点权大的点到点权小的点(可以相等)的情况下,从1点出发建立一棵尽可能有更多点的最小生成树

思路

考虑顺序

在这种多条件题目中,我们有一个优先考虑顺序(反正我是这样的啦),即先考虑是否可行,再考虑最优,比如这道题,我们就先考虑一些硬性的规定:只能从高滑到低,再考虑景点多、距离小这样的条件

1.只能从高到低

如果给你一张无向图,它已经满足每个景点都可以从1号景点按要求到达,那是不是这个点就解决了?那必须的呀,我们想要的就是这样一张图呀!所以现在考虑造出这样一张图。想想我们知道什么,只有每个景点的高度及每两个景点的连通性(距离我们暂时不需要),这就够了!对于每一条边,我们规定它的方向为从高的连向低的,让后从1号景点开始跑一遍BFS/DFS,所有能跑到的点以及经过的边组成一张新图,这张新图就是前面说的满足要求的图了

代码:

//tmp为记录能到的点数(题目要问)
//vis为记录是否以及遍历过这个点,防止不断递归
//g为原图
//g_new为新图
void dfs(int i){
	if(vis[i])return;
	tmp++;
	vis[i]=true;
	for(int j=0;j<g[i].size();j++){
		g_new[++cnt].u=i;
		g_new[cnt].v=g[i][j].u;
		g_new[cnt].w=g[i][j].w;
		dfs(g[i][j].u);
	}
	return;
}

2.到的景点最多、总距离最短

要让到的景点最多,肯定是能到的点都要到,也就是说,我们在刚才DFS遍历的时候就应该存储一下能到的点的个数,直接输出。

这就完了?开始考虑总距离最短?错!能到的点一定可以到没问题,但是不能光考虑总距离,如果光考虑总距离最小,就有可能出现反着爬坡的情况(因为最小生成树是不会考虑边的方向的),那怎么办?

在对边排序的时候,按终点的高度从大到小进行排序,这样就能从高到低下来了

还有个总距离最短的条件,这个简单,在高度的基础上,以边权为第二关键字,从小到大排即可

代码

bool cmp(edge x,edge y){
	if(hi[x.v]!=hi[y.v]){
		return hi[x.v]>hi[y.v];
	}
	return x.w<y.w;
}

最后就是计算最小生成树了,直接用模板即可,代码就不单独放了,免得又被你们说是凑字数

坑点

代码打好了,愉快的交上去,错了!什么鬼,是我写错了?请注意,这道题坑点有亿点点多

  1. 每条边的最大值是 1 0 9 10^9 109,而最多有 1 0 6 10^6 106条边,嗯。。。爆int稳稳的,该怎么改我不说了吧,高精啊!,long long啊!
  2. 相同高度是可以到达的,所以在建立原图的时候,如果两的点高度相同,需要建立两条边互相连接,这样后面DFS/BFS遍历的时候才不会错
  3. 都建立了两条边,边数量的最大值不改改?肯定要两倍快乐呀,不然RE稳稳的

最后就是代码

我知道你们只喜欢这个

#include
#include
#include
#include
using namespace std;
const int MAXN=100005;
const int MAXM=2000005;
struct node{
	int u;
	int w;
	node(){}
	node(int U,int W){
		u=U;
		w=W;
	}
};
struct edge{
	int u,v;
	int w;
}g_new[MAXM];
int hi[MAXN];
vector<node> g[MAXN];
bool vis[MAXN];
int cnt=0;
int tmp=0;
void dfs(int i){
	if(vis[i])return;
	tmp++;
	vis[i]=true;
	for(int j=0;j<g[i].size();j++){
		g_new[++cnt].u=i;
		g_new[cnt].v=g[i][j].u;
		g_new[cnt].w=g[i][j].w;
		dfs(g[i][j].u);
	}
	return;
}
int fa[MAXN];
int get(int x){
	if(fa[x]==x){
		return x;
	}
	return fa[x]=get(fa[x]);
}
bool cmp(edge x,edge y){
	if(hi[x.v]!=hi[y.v]){
		return hi[x.v]>hi[y.v];
	}
	return x.w<y.w;
}
long long kruskal(int m){
    long long sum=0;
    sort(g_new+1,g_new+1+m,cmp);
//	for(int i=1;i<=m;i++){
//		printf("%d %d %d\n",g_new[i].u,g_new[i].v,g_new[i].w);
//	}
    for(int i=1;i<=m;i++){
        int fu=get(g_new[i].u);
        int fv=get(g_new[i].v);
        if(fu!=fv){
            fa[fv]=fu;
            sum+=g_new[i].w;
        }
    }
    return sum;
}
int main(){
	int n,m;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&hi[i]);
	}
	int u,v,w;
	for(int i=1;i<=m;i++){
		scanf("%d %d %d",&u,&v,&w);
		if(hi[u]<hi[v]){
			swap(u,v);
		}
		g[u].push_back(node(v,w));
		if(hi[u]==hi[v]){
			g[v].push_back(node(u,w));
		}
	}
	dfs(1);
	for(int i=1;i<=n;i++){
		fa[i]=i;
	}
	printf("%d %lld",tmp,kruskal(cnt));
return 0;
}

如果我的题解对你有帮助,帮我把下面那个惨白的竖着大拇指的手点红呗

你可能感兴趣的:(最小生成树)