ZOJ 2318 Get Out!(计算几何+spfa好题!)

这篇博客探讨了一个计算几何问题,即如何确定一个圆是否位于其他多个相互连接的圆所构成的封闭区域内。作者介绍了将问题转化为图论模型的方法,特别是通过判断点在多边形内的角度跨度来构建边,并利用SPFA算法检查是否存在负环,从而解决原问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

你是一个圆,然后平面还有n个圆,问你能否通过移动自己,突出重围!再抽象一点,就是判断一个圆,是否在其他若干个彼此相连的圆的“大圈圈”内。。。

这里不得不强烈推荐watashi大神的神思路。。。神奇地将此题转化成图论模型!

个人的理解是,如果某两个圆相交或相切,那么他们组成的封闭区间可以用连接其两圆心的线段表示,这样能得到若干线段,这样就成了,若干线段中,是否能组成一个多边形,使得起始圆心在多边形内呢?

然后神思路就来了。。。判断一个点是否在多边形内,如果按顺时针扫描每一条边,依次计算经过该边的跨度(或者说仰视角),如果在点内,和为2PI,如果逆时针来,就是-2PI。 如果在多边形外的话,跨度和必然是0.于是就能根据这个性质建图喽~!以每个圆心为节点,两两相交的圆,根据两个圆心的顺/逆时针顺序,添加两条边,一个ang,一个-ang。然后就抽象成了是否存在一个负环。。。Orz。。。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
#define FF(i, a, b) for(int i=a; i<b; i++)
#define FD(i, a, b) for(int i=a; i>=b; i--)
#define REP(i, n) for(int i=0; i<n; i++)
#define PB push_back
#define CLR(a, b) memset(a, b, sizeof(a))
using namespace std;

const int maxn = 333;
const double eps = 1e-8;
struct Edge
{
    int to;
    double dist;
    Edge(int a=0, double b=0):to(a), dist(b){}
};

struct spfa
{
    int n, m;
    vector<int> G[maxn];
    vector<Edge> edges;
    int cnt[maxn];
    double d[maxn];
    bool inq[maxn];

    void init(int n)
    {
        this->n = n;
        REP(i, n) G[i].clear();
        edges.clear();
    }

    void AddEdge(int from, int to, double dist)
    {
        edges.PB(Edge(to, dist));
        m = edges.size();
        G[from].PB(m-1);
    }

    bool NegativeCycle()
    {
        queue<int> q;
        REP(i, n) d[i] = 0, inq[i] = true, cnt[i] = 0, q.push(i);
        while(!q.empty())
        {
            int u = q.front(); q.pop();
            inq[u] = false;
            REP(i, G[u].size())
            {
                Edge e = edges[G[u][i]];
                //不加eps是要wa的
                if(d[e.to] > d[u] + e.dist + eps)
                {
                    d[e.to] = d[u] + e.dist;
                    if(!inq[e.to])
                    {
                        q.push(e.to);
                        inq[e.to] = true;
                        if(++cnt[e.to] >= n) return true;
                    }
                }
            }
        }
        return false;
    }
}solver;

int T, n;
double x[maxn], y[maxn], r[maxn];
template <class T> T sqr(T x) { return x*x; }
double dist(int i, int j)
{
    return sqrt(sqr(x[i]-x[j]) + sqr(y[i]-y[j]));
}

int main()
{
    scanf("%d", &T);

    while(T--)
    {
        scanf("%d", &n);
        solver.init(n);

        REP(i, n) scanf("%lf%lf%lf", &x[i], &y[i], &r[i]);
        scanf("%lf%lf%lf", &x[n], &y[n], &r[n]);
        
        //强制回到点(0, 0)
        REP(i, n) x[i] -= x[n], y[i] -= y[n], r[i] += r[n];
        x[n] = y[n] = 0;

        REP(i, n) FF(j, i+1, n)
        {
            //不加eps就wa到死
            if(r[i] + r[j] - dist(i, j) < eps) continue;
            //flag 看顺时针或逆时针
            bool flag = (x[i]*y[j]-y[i]*x[j] >= 0);
            //向量夹角
            double ang = acos((x[i]*x[j]+y[i]*y[j]) / dist(i, n) / dist(j, n));
            //正反两条边
            solver.AddEdge(i, j, flag ? ang : -ang);
            solver.AddEdge(j, i, flag ? -ang : ang);
        }
        printf("%s\n", solver.NegativeCycle() ? "NO" : "YES");
        if(T) puts("");
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值