我的世界【图论】【拓扑排序】【堆】

10 篇文章 0 订阅
9 篇文章 0 订阅
本文介绍了如何利用图论、拓扑排序和堆的数据结构解决一道关于矿洞对应关系的问题。解题思路是从叶子节点开始搜索,通过拓扑排序和堆来确保危险程度的正确比较和字典序输出。当遇到无法找到对应矿洞的情况,输出-1。
摘要由CSDN通过智能技术生成

>Description
在这里插入图片描述


>Input
在这里插入图片描述

>Output
在这里插入图片描述
在这里插入图片描述


>Sample Input
4
3 1
1 2
4 2

>Sample Output
3 2 4 1

在这里插入图片描述


>解题思路

输出样例其实是A从小到大对应的序号,a3<a2<a4<a1 比赛的时候没看懂TT

我们会发现,对于一个节点的对应矿洞,对应矿洞的对应矿洞也同样是这个节点,也就是说,所有矿洞2个一组,每组中每个矿洞的对应矿洞是对方

所以我们可以从叶子节点开始搜起,因为叶子节点只有一条边,连的是它的父亲,所以叶子节点的对应矿洞只能是它的父亲,它的父亲的对应矿洞只能是当前这个叶子节点

这样我们两两一组,如果有多出来没有找到对应矿洞的我们就输出-1

题目还有一个重要的性质,一个点直接连接的除了对应矿洞的所有点,它的危险程度必须都大于对应矿洞,这里可以用到拓扑排序,再加上要字典序输出,使最小的数尽可能小,我们可以对拓扑排序中入度为0的点们做一个小根堆来维护


>代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 500005
using namespace std;

struct line
{
	int to, next;
} a[2 * N], aa[2 * N];
int n, t, h[N], tt, hh[N], g[N], r[N], ans[N], d[3 * N], cnt;

int read ()
{
	int l = 0;
	char c = getchar();
	while (c > '9' || c < '0') c = getchar();
	while (c >= '0' && c <= '9')
	{
		l = l * 10 + c - '0';
		c = getchar();
	}
	return l;
}
void write (int l)
{
	if (l > 9) write (l / 10);
	putchar (l % 10 + '0');
}
void add (int u, int v)
{
	a[++t] = (line) {v, h[u]}; h[u] = t;
	a[++t] = (line) {u, h[v]}; h[v] = t;
} //建原图
void addd (int u, int v)
{
	aa[++tt] = (line) {v, hh[u]}; hh[u] = tt;
} //建拓扑的图
void dfs (int now, int fath)
{
	for (int i = h[now]; i; i = a[i].next)
	  if (a[i].to != fath)
	    dfs (a[i].to, now);
	if (g[fath] || g[now]) return; //如果两个之中已经有一个配对了,这两个的组合就不能成立
	g[now] = fath, g[fath] = now;
	
	for (int i = h[now]; i; i = a[i].next)
	  if (a[i].to != fath)
	    r[a[i].to]++, addd (fath, a[i].to);
	    
	for (int i = h[fath]; i; i = a[i].next)
	  if (a[i].to != now)
	    r[a[i].to]++, addd (now, a[i].to); //建拓扑图
}
void up (int s)
{
	while (s / 2 > 0 && d[s / 2] > d[s])
	{
		swap (d[s], d[s / 2]);
		up (s / 2);
	}
}
void down (int s)
{
	int y;
	while ((s * 2 <= cnt && d[s * 2] < d[s])
	    || (s * 2 + 1 <= cnt && d[s * 2 + 1] < d[s]))
	{
		y = s * 2;
		if (d[s * 2 + 1] < d[s * 2]) y++;
		swap (d[s], d[y]);
		down (y);
	}
}

int main()
{
	n = read ();
	int u, v;
	for (int i = 1; i < n; i++)
	{
		u = read (), v = read ();
		add (u, v);
	}
	dfs (1, 0);
	for (int i = 1; i <= n; i++)
	  if (!g[i])
	  {
	  	putchar ('-'); putchar ('1');
	  	return 0;
	  }
	memset (d, 0x7f, sizeof (d));
	for (int i = 1; i <= n; i++)
	  if (!r[i]) //把入度为0的点加入小根堆
	  {
	  	d[++cnt] = i;
		up (cnt);
	  }
	for (int k = 1; k <= n; k++)
	{
		ans[k] = d[1]; //第k小的数为当前的堆顶
		d[1] = d[cnt];
		d[cnt] = d[0];
		cnt--;
		down (1);
		for (int i = hh[ans[k]]; i; i = aa[i].next)
		{
			r[aa[i].to]--;
			if (!r[aa[i].to]) //把入度为0的点加入堆
			{
				d[++cnt] = aa[i].to;
				up (cnt);
			}
		}
	}
	for (int i = 1; i <= n; i++)
	  write (ans[i]), putchar (' ');
	return 0;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值