情报机器人(robot) + 三分小结

9 篇文章 0 订阅
1 篇文章 0 订阅

题面

思路

很容易想到思路,算出每两个机器人之间的"相遇时间"(即何时记录信号),再用最小生成树(kruskal)算出答案。

难点:如何算出每两个机器人之间的“相遇时间”?

考场上我脑子一抽,就想枚举(略加亿点小技巧)每两个机器人之间的相对运动关系,可是打了30多种情况后就放弃了(共有64种可能,而且打的还不一定是对的),于是打了一个暴力枚举t就跑了 (结果只有8分) , 可是我就没有思考t和两个机器人的距离的关系。

建立一个直角坐标系,以tx轴,以两机器人之间的距离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:第二种打法由于没有例题,笔者无法检验正确性,恳求大佬帮助

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值