题面
思路
很容易想到思路,算出每两个机器人之间的"相遇时间"(即何时记录信号),再用最小生成树(kruskal)算出答案。
难点:如何算出每两个机器人之间的“相遇时间”?
考场上我脑子一抽,就想枚举(略加亿点小技巧)每两个机器人之间的相对运动关系,可是打了30多种情况后就放弃了(共有64种可能,而且打的还不一定是对的),于是打了一个暴力枚举t就跑了 (结果只有8分) , 可是我就没有思考t和两个机器人的距离的关系。
建立一个直角坐标系,以t为x轴,以两机器人之间的距离为y轴,函数图像成一个单峰函数。 (易知)
虽然笔(ju)者(ruo)无法用数学的方法为读(ju)者(lao)们证明,但大致还是可以讲讲为什么的 (简单点说就是相信玄学) ,成单峰函数说明先是成"追击问题",再是成相遇并超越的(也有可能永远不会相遇),而两个机器人的运动方向和速度是不变的,所以两个机器人的每次变化都是呈规律的,所以不可能有多个峰顶。
强行伪证,凭感觉做题
所以我们可以用三分求解两个机器人之间的"相遇时间"
参考代码
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;
const int MAXN = 1005;
const int MAXM = MAXN * MAXN / 2;
const LL INF = 0x7f7f7f7f7f7f7f;
int n, len, cnt;
int fa[MAXN], _rank[MAXN];
//_rank用来存储树的节点数,用来判断能不能全部联通
int tox[MAXN] = { 0, 1, -1, 0, 0, 1, 1, -1, -1 };
int toy[MAXN] = { 0, 0, 0, 1, -1, 1, -1, 1, -1 };
struct node {
int x, y, tox, toy;
//tox表示x轴方向上的变化量,toy同理
} a[MAXN];
struct edge {
int u, v;
LL val;
edge() {}
edge(int U, int V, LL VAL) { u = U, v = V, val = VAL; }
} e[MAXM];
LL dis(LL, LL, LL, LL);//算出两点间的距离
LL query(int, int, LL);
//传入机器人的编号和时间,算出"距离"
LL Abs(LL x) { return x > 0 ? x : -x; }
int FindSet(int);
void MakeSet();
void UnionSet(int, int, int);
bool cmp(edge x, edge y) { return x.val < y.val; }//kruskal的排序
int main() {
cin >> n;
MakeSet();
for (int i = 1; i <= n; i++) {
int type;
cin >> a[i].x >> a[i].y >> type;
a[i].tox = tox[type];
a[i].toy = toy[type];
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
LL l = 0, r = INF, ans = -1;
while (l < r) {//三分求解(模板)
LL midl = (l + r) >> 1;
LL midr = midl + 1;
if (query(i, j, midl) <= query(i, j, midr))
r = midl;
else if (query(i, j, midl) > 0)
l = midl + 1;
else
r = midl - 1, ans = midl;
}
if (query(i, j, l) <= 0)
ans = l;
if (ans != -1) {//如果ans改变过(两个机器人能相遇)
e[++len] = edge(i, j, ans);
}
}
}
sort(e + 1, e + 1 + len, cmp);
for (int i = 1; i <= len; i++) {
UnionSet(e[i].u, e[i].v, e[i].val);//kruskal
}
int root = FindSet(1);
if (_rank[root] != n)//判断连通性
cout << "No solutions";
else
cout << cnt;
return 0;
}
LL query(int i, int j, LL t) {
LL x = a[i].x + a[i].tox * t;//t秒后i号机器人的x坐标
LL y = a[i].y + a[i].toy * t;//同理
LL xx = a[j].x + a[j].tox * t;//同理
LL yy = a[j].y + a[j].toy * t;//同理
return dis(x, y, xx, yy) - t * 2;
//由于ta的信号区域要拓展,即曼哈顿距离 + 1
//而两个机器人都要拓展,所以ta们的信号范围为2 * t
}
LL dis(LL x, LL y, LL xx, LL yy) { return Abs(x - xx) + Abs(y - yy); }
int FindSet(int x) {
if (fa[x] != x) {
fa[x] = FindSet(fa[x]);
}
return fa[x];
}
void MakeSet() {
for (int i = 1; i <= n; i++) {
fa[i] = i;
_rank[i] = 1;
}
}
void UnionSet(int x, int y, int val) {
int u = FindSet(x), v = FindSet(y);
if (u == v)
return;
cnt = val;
fa[u] = v;
_rank[v] += _rank[u];
}
三分小结
三分(浮点,求凸点)
double l = L, r = R;
while (l + esp < r) {
double midl = l + (r - l) / 3;
double midr = r - (r - l) / 3;
if (query (midl) > query (midr)) r = midr;
else l = midl;
}
return l;
三分(整形,求凸点)
1.
int l = L, r = R, ans = -1;
while (l < r) {
int midl = (l + r) >> 1;
int midr = midl + 1;
if (query (midl) <= query (midr)) r = midl - 1;
else l = midl + 1;
ans = Max (ans, query (midl));
}
2.
int l = L, r = R, ans;
while (l + 2 < r) {
int midl = (l + r) >> 1;
int midr = midl + 1;
if (query (midl) > query (midr)) r = midr;
else l = midl;
}
int mid = (l + r) >> 1;
if (query (l) > query (y) && query (l) > query (mid)) ans = l;
else if (query (mid) > query (l) && query (mid) > query (r)) ans = mid;
else ans = r;
ps:第二种打法由于没有例题,笔者无法检验正确性,恳求大佬帮助