HDU 3832 Earth Hour

传送门

三点连通。
给你一个平面,上面有很多个点,给你每个点的坐标,每个点还有个半径,两个点之间有一条无向边当且仅当以两点为圆心的两圆相切或相交。在给定的三个关键点之间互相连通的基础上,问你最多拿走多少个点(拿走的这些点相当于不存在了,完全不参与那三个关键点的连通)。

毫无疑问,这题首先我们得建边,比较两圆心的距离和半径之和即可。然后这个无向图就出来了。然后考虑一下,这题问的是最多拿走多少点,那相当于求最少多少个点参与(包括这三个点) 能使这三个点连通。
这个问题可以转化为找一个点,这个点到三个关键点的最小途径点数最小。(不要问我怎么想出来的)

怎么求一个点到另一个点的最小途径点数?很巧妙,转化为最小途径边数+1,而最小途径边数又可以转化为求解边权都是1的最短路。为什么能这么转化?因为找到的最短路肯定是简单路,不会有环这种东西。之后,因为是无向图,以这三个关键点分别为源点求最短路就好了。

(将 边数 / 点数 最大 / 最小 转化为边权、转化为最短路问题的思想)

下面我来说一点自己的思考。
这个做法只适用于三个点以内,四个点就不行了。设想四个点在一条链上(当然,四个点也可以有别的构型),那么上述枚举的最小值无论如何都会重复计算边数,得到的一定是错误的答案。但是,三个点不会有这种情况。三个点的两种构型(链、奔驰车标型)都可以找到一个中介点,从中介点到三个点的路径都不重叠。


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

const int INF = 1e9;
const int MAX = 200 + 5;
int N, T;
int ans;

int g[MAX][MAX];
int d[4][MAX];
bool inq[MAX];

struct Point
{
	int x, y, r;
};
Point p[MAX];

void init()
{
	ans = -1;
}

double dist(Point p1, Point p2)
{
	return sqrt(pow((double)(p1.x - p2.x), 2.0) + pow((double)(p1.y - p2.y), 2.0));
}     // 需要输出double,所以为了稳妥,让参数都变成double

void spfa(int s)
{
	queue<int> q;
	memset(inq, 0, sizeof inq);
	fill(d[s] + 1, d[s] + N + 1, INF);

	q.push(s);
	inq[s] = true;
	d[s][s] = 0;

	for (; !q.empty();)
	{
		int x = q.front();
		q.pop();
		inq[x] = false;
		for (int i = 1; i <= N; i++)
		{
			if (g[x][i] != INF && d[s][x] + g[x][i] < d[s][i])
			{
				d[s][i] = d[s][x] + g[x][i];
				if (!inq[i])
				{
					q.push(i);
					inq[i] = true;
				}
			}
		}
	}
}

int main()
{
	scanf("%d", &T);
	for (; T--;)
	{
		scanf("%d", &N);
		init();
		for (int i = 1; i <= N; i++)
			scanf("%d%d%d", &p[i].x, &p[i].y, &p[i].r);
		
		for (int i = 1; i <= N; i++)
		{
			g[i][i] = 0;
			for (int j = i + 1; j <= N; j++)
			{
				if (dist(p[i], p[j]) > (double)(p[i].r + p[j].r))  // 判断两圆是否相切或相交
					g[i][j] = g[j][i] = INF;
				else g[i][j] = g[j][i] = 1;                        // 将点最少转化为边最少,从而可以最短路求解
			}
		}
		spfa(1);
		spfa(2);
		spfa(3);
		if (d[1][2] == INF || d[1][3] == INF || d[2][3] == INF)
		{
			printf("-1\n");
			continue;
		}
		for (int i = 1; i <= N; i++)
		{
			if (d[1][i] != INF && d[2][i] != INF && d[3][i] != INF)       // 没这句就WA(好几个INF会溢出的),可见注意细节的重要性
				ans = max(ans, N - (d[1][i] + d[2][i] + d[3][i] + 1));    // 别忘了还有个1
		}
		printf("%d\n", ans);
	}

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值