SGU128 Snake

SGU128 Snake

题目大意

给出平面上N个点,要求用闭合无自交的折线连接起来
其中线段端点一定是给出的点,点要无重复无遗漏,相邻线段构成直角,线段要平行于坐标轴
找出总长度最短的折线

算法思路

由于线段端点必须构成直角,且不能自交
因此,每个点都必须要连出一条平行x轴的线段,和一条平行于y轴的线段
考虑y坐标等于某个值的所有点,最左边的点只能向右连,以此类推,整行的连线都是确定的
对于x坐标同样,因此,最终的连线方案是确定的,可以通过排序确定
判断是否无重复无遗漏、计算答案等用dfs可以轻易解决
而是否存在自交,需要使用扫描线进行判断
把每条横线的两端设为进入与离开事件,每条竖线是查询事件
按照x坐标排序后依次执行,插入、删除某个y坐标,查询一段区间内是否存在y坐标即可
可以使用set维护,注意x坐标相同时,事件的执行顺序(先删除再查询再插入)

时间复杂度: O(NlogN)

代码

/**
 * Copyright © 2015 Authors. All rights reserved.
 * 
 * FileName: 128.cpp
 * Author: Beiyu Li <sysulby@gmail.com>
 * Date: 2015-06-13
 */
#include <bits/stdc++.h>

using namespace std;

#define rep(i,n) for (int i = 0; i < (n); ++i)
#define For(i,s,t) for (int i = (s); i <= (t); ++i)
#define foreach(i,c) for (__typeof(c.begin()) i = c.begin(); i != c.end(); ++i)

typedef long long LL;
typedef pair<int, int> Pii;

const int inf = 0x3f3f3f3f;
const LL infLL = 0x3f3f3f3f3f3f3f3fLL;

const int maxn = 10000 + 5;

int n, sz;
int x[maxn], y[maxn], id[maxn];
struct Event {
        int x, t, y, l, r;
        Event() {}
        Event(int x, int t, int y): x(x), t(t), y(y) {}
        Event(int x, int t, int l, int r): x(x), t(t), l(l), r(r) {}
        bool operator<(const Event &e) const
        { return x ^ e.x? x < e.x: t < e.t; }
} vec[maxn*2];
multiset<int> st;
multiset<int>::iterator it;
bool vis[maxn];
map<int, Pii> adj[maxn];

int dfs(int u, int d, int dep)
{
        if (!adj[u].count(d)) return 0;
        vis[u] = true;
        int v = adj[u][d].first, w = adj[u][d].second;
        if (vis[v]) return dep == n - 1? w: 0;
        int ret = dfs(v, d ^ 1, dep + 1);
        return ret? ret + w: 0;
}

int cmpx(int a, int b) { return x[a] ^ x[b]? x[a] < x[b]: y[a] < y[b]; }
int cmpy(int a, int b) { return y[a] ^ y[b]? y[a] < y[b]: x[a] < x[b]; }
int solve()
{
        rep(i,n) id[i] = i;
        sort(id, id + n, cmpy);
        for (int i = 0, j; i < n; i = j) {
                for (j = i + 1; j < n && y[id[j]] == y[id[i]]; ++j);
                if ((j - i) & 1) return 0;
                for (int k = i; k < j; k += 2) {
                        int u = id[k], v = id[k+1];
                        adj[u][0] = Pii(v, x[v] - x[u]);
                        adj[v][0] = Pii(u, x[v] - x[u]);
                        vec[sz++] = Event(x[u], 1, y[u]);
                        vec[sz++] = Event(x[v], -1, y[v]);
                }
        }
        rep(i,n) id[i] = i;
        sort(id, id + n, cmpx);
        for (int i = 0, j; i < n; i = j) {
                for (j = i + 1; j < n && x[id[j]] == x[id[i]]; ++j);
                if ((j - i) & 1) return 0;
                for (int k = i; k < j; k += 2) {
                        int u = id[k], v = id[k+1];
                        adj[u][1] = Pii(v, y[v] - y[u]);
                        adj[v][1] = Pii(u, y[v] - y[u]);
                        vec[sz++] = Event(x[u], 0, y[u], y[v]);
                }
        }
        sort(vec, vec + sz);
        rep(i,sz) {
                if (vec[i].t < 0) st.erase(st.find(vec[i].y));
                if (vec[i].t > 0) st.insert(vec[i].y);
                if (!vec[i].t) {
                        it = st.upper_bound(vec[i].l);
                        if (it != st.end() && *it < vec[i].r) return 0;
                }
        }
        return dfs(0, 0, 0);
}

int main()
{
        scanf("%d", &n);
        rep(i,n) scanf("%d%d", &x[i], &y[i]);
        printf("%d\n", solve());

        return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值