ACM-ICPC 2018 徐州赛区网络预赛 J.Maze Designer 最大生成树+LCA求距离


ACM-ICPC 2018 徐州赛区网络预赛 J.Maze Designer


标签

  • 生成树
  • 树剖LCA

前言

  • 还好,不难,就是没做过类似的题,一时间没弄明白题目在问什么…

简明题意

  • 有一个n * m个排成矩形的节点,题目会给出这些点相互之间的连接,以及边权。每连一条边需要一个花费。而题目是这样要求的:每连一条边需要一个花费,而没有连的边是可以相互到达的,现在需要所有的点两两间存在唯一路径到达且建边的花费最小,在此前提下,给出q组询问,每组询问两点之间的步数。

思路

  • 首先,如果说两两之间存在唯一路径,也就是说说有的点形成了一棵树
  • 其次,思考树是怎么形成的?一开始,是一张图,现在需要去掉一些边,使成为一棵树,且去掉的边权之和需要最小
  • 那么我们倒过来想,既然要成一棵树,我们直接想哪些边不用去掉?生成树上的边不用去掉,显然生成树上的边权越大,去掉的边权值和也就最小了,所以我们应该去跑一棵最大生成树
  • 跑完生成树,需要询问两点之间的简单路径长。那么dfs预处理,很简单

注意事项


总结

  • 如果说点两两之间存在唯一路径,也就是说所有的点形成了一棵树
  • 对于一个图上的生成树,题目可能问你生成树的最大/小权,也可能让你最大/小化删去的边权。在这一题中,题目把边说成是强,需要你造墙,这就可能让人产生错觉,以为造墙就是造边,而实际上,造墙是去掉边

AC代码

#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;

const int maxn = 500 * 500 + 10;

struct Edge
{
	int u, v, w;
	Edge(int u, int v, int w) : u(u), v(v), w(w) {}
	bool operator < (const Edge& a)const
	{
		return w < a.w;
	}
};

int n, m;
vector<int> g[maxn];

priority_queue<Edge> que;

int fa0[maxn];
int __find(int u)
{
	if (fa0[u] == 0)
		return u;
	return fa0[u] = __find(fa0[u]);
}
void __union(int u, int v)
{
	int pa = __find(u), pb = __find(v);
	if (pa != pb)
		fa0[pa] = pb;
}

int getid(int i, int j)
{
	return j + (i - 1) * m;
}

int dep[maxn], fa[maxn], siz[maxn], son[maxn], dis[maxn];
void dfs1(int u, int f, int deep, int tot)
{
	dep[u] = deep;
	fa[u] = f;
	siz[u] = 1;
	dis[u] = tot;

	int max_son = -1;
	for (auto& v : g[u])
	{
		if (v != f)
		{
			dfs1(v, u, deep + 1, tot + 1);
			siz[u] += siz[v];
			if (siz[v] > max_son)
				max_son = siz[v], son[u] = v;
		}
	}
}

int top[maxn], id[maxn], cnt;
void dfs2(int u, int topf)
{
	top[u] = topf;
	id[u] = ++cnt;

	if (son[u])
	{
		dfs2(son[u], topf);
		for (auto& v : g[u])
			if (v != fa[u] && v != son[u])
				dfs2(v, v);
	}
}

int LCA(int u, int v)
{
	while (top[u] != top[v])
	{
		if (dep[top[v]] < dep[top[u]]) swap(u, v);
		v = fa[top[v]];
	}
	return dep[u] < dep[v] ? u : v;
}

bool can_use[maxn];
void solve()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
		{
			char cmd[10];
			int w;

			scanf("%s%d", cmd, &w);
			if (cmd[0] == 'D')
			{
				que.push(Edge(getid(i, j), getid(i + 1, j), w)); 

			}
				
			scanf("%s%d", cmd, &w);
			if (cmd[0] == 'R')
			{
				que.push(Edge(getid(i, j), getid(i, j + 1), w));
	
			}
		}

	//kruskal
	while (!que.empty())
	{
		int u = que.top().u, v = que.top().v, w = que.top().w;
		que.pop();
		if (__find(u) == __find(v))
			continue;

		g[u].push_back(v), g[v].push_back(u);
		can_use[u] = 1, can_use[v] = 1;
		__union(u, v);
	}
	
	//树剖
	dfs1(1, 1, 1, 0);
	dfs2(1, 1);
		
	//处理询问
	int q;
	scanf("%d", &q);
	while (q--)
	{
		int a, b, c, d;
		scanf("%d%d%d%d", &a, &b, &c, &d);

		int u = getid(a, b), v = getid(c, d);
		printf("%d\n", dis[u] + dis[v] - 2 * dis[LCA(u, v)]);
	}
}

int main()
{
	freopen("Testin.txt", "r", stdin);
	solve();
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值