洛谷 P2510 [HAOI2008]下落的圆盘 题解

题目传送门

题目大意是计算几何, n n n 个圆在平面上编号大的圆将编号小的圆覆盖求最后所有没有被覆盖的圆的边缘的总长度。

在做这道题之前有几个前置知识。

极坐标系:在平面内由极点、极轴和极径组成的坐标系。

如:在平面上取一点 O O O 叫做极点,从 O O O 出发引一条射线 O x Ox Ox 称为极轴。通常规定角度取逆时针方向为正。

极角:在极坐标系中,平面上任何一点到极点的连线和极轴的夹角叫做极角。

那么,我们可以发现极角的大小为 0 ∼ 360 0\sim360 0360 度(考虑正角)。

极角可以进行排序,(由小到大的那种),那么对于平面上一个点 ( x , y ) (x,y) (x,y) 到极点的连线和极轴 x x x 的夹角大小为 atan2( y ÷ x y \div x y÷x)。

atan2 指的 t a n tan tan反函数方位角

atan2 比 atan 稳定,所以我们使用 atan2。

但是我们要求出极角来,这个返回的是方位角,如果当前角度为正,那么就是极角, 如果为负,我们需要将其加上 2 π 2\pi 2π,就变成极角了。

接下来就可以做这道题了,首先考虑两圆相交如何求夹角?

余弦定理acos函数即可。 n 2 n^2 n2 求交,然后我们发交的地方只算一次,所以可以利用极角来做。

求出所有的极角之后,按极角排序,然后就是直线的覆盖问题。

注意一下覆盖弧度范围跨越 0 0 0 2 π 2\pi 2π 的处理。

#include <bits/stdc++.h>
#define N 1010
#define squ(x) ((x) * (x))
using namespace std;
const double pi = acos(-1);
struct data
{
    double pl , pr;
    bool operator<(const data &a)const {return pl < a.pl;}
}a[N << 1];
double x[N] , y[N] , r[N];
int tot;
int main()
{
    int n , i , j;
    double afa , beta , d , last , ans = 0;
    scanf("%d" , &n);
    for(i = 1 ; i <= n ; i ++ ) scanf("%lf%lf%lf" , &r[i] , &x[i] , &y[i]);
    for(i = 1 ; i <= n ; i ++ )
    {
        ans += 2 * pi * r[i];
        tot = 0;
        for(j = i + 1 ; j <= n ; j ++ )
        {
            tot ++ , d = squ(x[i] - x[j]) + squ(y[i] - y[j]);
            if(squ(r[i] + r[j]) <= d) a[tot].pl = a[tot].pr = 0;
            else if(squ(r[i] - r[j]) >= d)
            {
                if(r[i] > r[j]) a[tot].pl = a[tot].pr = 0;
                else a[tot].pl = 0 , a[tot].pr = 2 * pi;
            }
            else
            {
                afa = acos((r[i] * r[i] + d - r[j] * r[j]) / (2 * r[i] * sqrt(d)));
                beta = atan2(y[j] - y[i] , x[j] - x[i]);
                if(beta < 0) beta += 2 * pi;
                a[tot].pl = beta - afa , a[tot].pr = beta + afa;
                if(a[tot].pl < 0) tot ++ , a[tot].pl = a[tot - 1].pl + 2 * pi , a[tot - 1].pl = 0 , a[tot].pr = 2 * pi;
                else if(a[tot].pr > 2 * pi) tot ++ , a[tot].pr = a[tot - 1].pr - 2 * pi , a[tot - 1].pr = 2 * pi , a[tot].pl = 0;
            }
        }
        sort(a + 1 , a + tot + 1);
        last = -1;
        for(j = 1 ; j <= tot ; j ++ )
        {
            if(a[j].pr <= last) continue;
            if(a[j].pl > last) ans -= (a[j].pr - a[j].pl) * r[i];
            else ans -= (a[j].pr - last) * r[i];
            last = a[j].pr;
        }
    }
    printf("%.3lf\n" , ans);
    return 0;//完美收官QwQ
}
这道题目还可以使用树状数组或线段树来实现,时间复杂度也为 $\mathcal{O}(n\log n)$。这里给出使用树状数组的实现代码。 解题思路: 1. 读入数据; 2. 将原数列离散化,得到一个新的数列 b; 3. 从右往左依次将 b 数列中的元素插入到树状数组中,并计算逆序对数; 4. 输出逆序对数。 代码实现: ```c++ #include <cstdio> #include <cstdlib> #include <algorithm> const int MAXN = 500005; struct Node { int val, id; bool operator<(const Node& other) const { return val < other.val; } } nodes[MAXN]; int n, a[MAXN], b[MAXN], c[MAXN]; long long ans; inline int lowbit(int x) { return x & (-x); } void update(int x, int val) { for (int i = x; i <= n; i += lowbit(i)) { c[i] += val; } } int query(int x) { int res = 0; for (int i = x; i > 0; i -= lowbit(i)) { res += c[i]; } return res; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); nodes[i] = {a[i], i}; } std::sort(nodes + 1, nodes + n + 1); int cnt = 0; for (int i = 1; i <= n; ++i) { if (i == 1 || nodes[i].val != nodes[i - 1].val) { ++cnt; } b[nodes[i].id] = cnt; } for (int i = n; i >= 1; --i) { ans += query(b[i] - 1); update(b[i], 1); } printf("%lld\n", ans); return 0; } ``` 注意事项: - 在对原数列进行离散化时,需要记录每个元素在原数列中的位置,便于后面计算逆序对数; - 设树状数组的大小为 $n$,则树状数组中的下标从 $1$ 到 $n$,而不是从 $0$ 到 $n-1$; - 在计算逆序对数时,需要查询离散化后的数列中比当前元素小的元素个数,即查询 $b_i-1$ 位置上的值; - 在插入元素时,需要将离散化后的数列的元素从右往左依次插入树状数组中,而不是从左往右。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值