CF33D Knights(图论建模(+虚根技巧) + lca,好题)

link

这道题一眼看上去就很不一样,但从题意的 m + 1 个区域和 m 个栅栏很容易想到,这应该是个树上的问题。

题目里的圆有很好的性质:圆之间不相交,只存在包含与相离的位置关系

摸一下样例,发现就是一个起点,必须越过多少个圆能到达终点

如果把这些圆抽象地理解一下,就是(u, v)路径上的点,同心圆一样层层包含的圆就像树上一个一个点往上跳,组成一颗子树,而包含不同圆的大圆就像这些子树的公共祖先,有一种父子关系在里边。

这个性质很完美,太完美了,这样不就建个图跑 lca 求路径长度就好了吗?哈哈

思路就这么简单,但图论建模一是难在建模思路,二就是怎样实现了。(我代码也码了差不多 40 分钟 ...)


在这里我列出一下我建图时遇到的一些要点:

  • 要逐层建边,不能跃层,要不然就会成环,这里我用 \(O(m^2)\) 对每个圆打擂台,找 包含它的最小的圆

  • 对于每个点(题目中的控制点),要找到 包含它的最小的圆,也就是对应着它在树上的点 ,也可以 \(O(m)\) 打擂台(这里都要用到一些简单的几何知识求距离,很简单)

  • 还有一个 trick,就是如果一个控制点没有圆包含它,只是这样做的话,它就无法进入树中,这里可以 建立虚根,就像在坐标图中做一个极大的圆把所有的圆都包含进去,这样就保证了所有的点都可以有树中对应的位置,同时,虚根对所有点都相当于增加了一个深度,对结果的 路径距离(相对值) 没有影响。

最后答案就是路径长度 \(dep[u] + dep[v] - 2 \cdot dep[~lca(u, v)~]\)

\(O(km)\) .

#include <bits/stdc++.h>
#define re register int 

using namespace std;
const int N = 3e5 + 10, logN = 50;
const double inf = 2000000010.00;

struct Edge
{
	int to, next;
}e[N << 1];
int top, h[N];
int dep[N], f[N][logN];

struct Node
{
	double x, y;
}a[N];
struct Round
{
	double x, y, r;
}pai[N];
int n, m, k;

inline double cale1(Round i, Round j)
{
	return sqrt((i.x - j.x) * (i.x - j.x) + (i.y - j.y) * (i.y - j.y));
}

inline double cale2(Node i, Round j)
{
	return sqrt((i.x - j.x) * (i.x - j.x) + (i.y - j.y) * (i.y - j.y));
}

inline void add(int x, int y)
{
	e[++ top] = (Edge){y, h[x]};
	h[x] = top;
}

inline void build()
{
	// 逐层建边,不能跃层 
	double delta = inf, tag;
	for (re i = 1; i <= m; i ++)
	{
		delta = inf, tag = 0;
		
		for (re j = 1; j <= m; j ++)
		{
			if (j == i) continue;
			
			double w = cale1(pai[i], pai[j]);
			if (w > pai[i].r + pai[j].r || pai[j].r < pai[i].r) continue; // 相离 或 内包含圆 
			
			if (w == 0)
			{
				if (pai[j].r - pai[i].r < delta)
				{
					delta = pai[j].r - pai[i].r;
					tag = j;
				}
			}
			else
			{
				if (pai[j].r - 2 * pai[i].r < delta)
				{
					delta = pai[j].r - 2 * pai[i].r;
					tag = j;
				}  
			}
		}
		
		if (delta == inf) add(i, 0), add(0, i);
		else add(i, tag), add(tag, i);
	}	
}

int dfs(int u, int fa)
{
	if (fa != -1) dep[u] = dep[fa] + 1;
	
	f[u][0] = fa;
	for (re i = 1; i <= log2(m); i ++)
		f[u][i] = f[f[u][i - 1]][i - 1];
		
	for (re i = h[u]; i; i = e[i].next)
	{
		int v = e[i].to;
		
		if (v == fa) continue;
		dfs(v, u);
	}
}

inline int lca(int u, int v)
{
	if (dep[u] < dep[v]) swap(u, v);
	
	for (re i = log2(m); i >= 0; i --)
		if (dep[f[u][i]] >= dep[v]) u = f[u][i];
		
	if (u == v) return u;
	
	for (re i = log2(m); i >= 0; i --)
		if (f[u][i] != f[v][i]) u = f[u][i], v = f[v][i];
	
	return f[u][0];
}

inline int get(int x)
{
	double delta = inf;
	int tag = 0;
	
	for (re i = 1; i <= m; i ++)
	{
		double d = cale2(a[x], pai[i]);
		
		if (d > pai[i].r) continue;
		
		if (pai[i].r - d < delta)
		{
			delta = pai[i].r - d;
			tag = i;
		}
	}
	return tag;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
	cin >> n >> m >> k;
	for (re i = 1; i <= n; i ++) cin >> a[i].x >> a[i].y;
	for (re i = 1; i <= m; i ++) cin >> pai[i].r >> pai[i].x >> pai[i].y;
	
	build();
	dep[0] = 1;
	dfs(0, -1);

	while (k --)
	{
		int a, b; cin >> a >> b;
		int u = get(a), v = get(b);
		
		cout << dep[u] + dep[v] - 2 * dep[(lca(u, v))] << '\n';
	}
	
	return 0;
}
posted @ 2024-06-24 22:42  Zhang_Wenjie  阅读(47)  评论(0)    收藏  举报