bzoj 2753 [SCOI2012]滑雪与时间胶囊

【题意】

给你一些边, 这些边的方向都是从高的地方到地的地方, 问1号点能到达的所有点的最小树形图。

【题解】

考虑在有向图上为什么不可以用Kruskal, 因为插入了一条有向边后, 当且仅当这条边是从 最后建成的最小树形图中靠近根的一侧 指向 远离根的一侧(即 这条边没有被指向的一端是被指向的一端的父亲), 插入这条边才有意义, 而一个点在最后的最小树形图中的状态在建树的过程中是很难直接判断的。 但是考虑这道题, 我们已知在最后建成的最小树形图中, 一个点是另一个点的祖先节点 仅当 它的高度大于另一个点的高度。       这时候可以发现原先Kruskal遇到的瓶颈问题 即可以完美解决:将所有边 按 被指向 一端的高度 降序排序,这时候再扫一遍Kruskal 的时候, 访问到第i 条边时, 之前加入最小生成树中的点 高度都大于 这个点, 即不可能成为这个点的儿子节点, 所以都可以加入树中。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 1000005
using namespace std;
int n, m, a[MAXN], fat[MAXN], head[MAXN], ss, kk, ee;
long long ans = 0;
bool vis[MAXN];
struct Edge{
	int u, v, next, k;
}edge[MAXN * 2];
inline void addedge(int u, int v, int k){edge[++ ee].v = v; edge[ee].u = u; edge[ee].k = k; edge[ee].next = head[u]; head[u] = ee;}
bool cmp(Edge x, Edge y){if(a[x.v] == a[y.v])return x.k < y.k; return a[x.v] > a[y.v];}
int find(int x){return (fat[x] == x) ?  x: fat[x] = find(fat[x]);}
void dfs(int x){if(!vis[x])ss ++; vis[x] = 1; for(int i = head[x]; i != -1; i = edge[i].next)if(!vis[edge[i].v])dfs(edge[i].v);}
int main()
{
	scanf("%d%d", &n, &m);
	memset(head, -1, sizeof(head));
	for(int i = 1; i <= n; i ++)scanf("%d", &a[i]);
	for(int i = 1; i <= m; i ++){int uu, vv, k;
		scanf("%d%d%d", &uu, &vv, &k);
	 	if(a[uu] >= a[vv])addedge(uu, vv, k);
	 	if(a[vv] >= a[uu])addedge(vv, uu, k);
	}dfs(1);
	sort(edge + 1, edge + ee + 1, cmp);
	for(int i = 1; i <= n; i ++)fat[i] = i;
	for(int i = 1; i <= ee; i ++)if(vis[edge[i].u] && vis[edge[i].v]){
		int fu = find(edge[i].u), fv = find(edge[i].v);
		if(fu != fv){
			fat[fv] = fu; ans += (long long)edge[i].k;
		}
	}printf("%d %lld\n", ss, ans);
	return 0;
}

【技巧总结】

这道题需要对Kruskal算法有较为清晰的认知, Kruskal的思想虽然很简单, 但是可以有一些非常漂亮的变形, 要总结一下, 基本上是OI最常用的算法之一了。

明明是一道知道了一个trick之后就完全sb的题, 被我WA了好久。。

因为没有考虑如果两个点的高度相同的时候就要把两条边都加上,,然后竟然有RE, 因为加了另外的边之后忘记开大数组了,,,,,,

我真的弱的不能忍啊,,这么水的题。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值