洛谷P1378 油滴扩展

28 阅读3分钟

原题:P1378 油滴扩展

题面:

P1378 油滴扩展

题目描述

在一个长方形框子里,最多有 NN 个相异的点,在其中任何一个点上放一个很小的油滴,那么这个油滴会一直扩展,直到接触到其他油滴或者框子的边界。必须等一个油滴扩展完毕才能放置下一个油滴。那么应该按照怎样的顺序在这 NN 个点上放置油滴,才能使放置完毕后所有油滴占据的总面积最大呢?(不同的油滴不会相互融合)

注:圆的面积公式 S=πr2S = \pi r^2,其中 rr 为圆的半径。

输入格式

第一行,一个整数 NN

第二行,四个整数 x,y,x,yx, y, x', y',表示长方形边框一个顶点及其对角顶点的坐标。

接下来 NN 行,第 ii 行两个整数 xi,yix_i, y_i,表示盒子内第 ii 个点的坐标。

输出格式

一行,一个整数,长方形盒子剩余的最小空间(结果四舍五入输出)。

输入输出样例 #1

输入 #1

2
20 0 10 10
13 3
17 7

输出 #1

50

说明/提示

对于 100%100\% 的数据,1N61 \le N \le 6,坐标范围在 [1000,1000][-1000, 1000] 内。

SolutionSolution

很久没有做搜索题了,所以最近准备练一下搜索。

看一眼题目,感觉比较毒瘤,是带浮点数计算的,但思路并不难,由于 n6n \le 6 ,所以我们根本不需要任何剪枝,直接暴力搜索方案然后求得剩余面积最小值即可。

这道题主要的是如何计算每个油滴最多能扩展的大小。题目说:当油滴碰到其他油滴或者矩形的边界的时候,就停止扩展。所以对于每个油滴,我们需要计算从它到周围最近的油滴或者矩形边界的距离,而这个距离就是它能扩展的半径。

具体地,在搜索的时候,我们用一个 rr 数组来记录前面已经用过的油滴的半径,然后枚举计算当前油滴和前面油滴间的距离,取距离的最小值。同时需要注意,设两个油滴间的距离为 disdis ,然后前面的油滴的扩展半径为 rjr_j ,若 disrjdis \le r_j ,则说明当前这个油滴已经被前面这个油滴覆盖,不能再计入扩展面积,直接跳过。

然后如果一个油滴没有被任何油滴覆盖,则取得它与其他油滴的最小距离后,再计算它到矩形四条边边界的最小距离,最终取最小值,即为当前油滴的扩展半径,计入总面积后,进行下一步搜索即可。

CodingCoding

#include <iostream>
#include <cstring>
#include <iomanip>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;

#define ll long long
#define ull unsigned long long
#define debug(x) cout << #x << "=" << x << "\n";

int n;
const int maxn = 10;
const double pi = 3.1415926535;
double x[maxn], y[maxn];
double r[maxn];
bool used[maxn];
double xa, ya, xb, yb;
double ans, area;

double calc_dis(double x1, double y1, double x2, double y2)
{
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

double calc_area(double r)
{
    return pi * r * r;
}

void dfs(double cur)
{
    ans = min(ans, area - cur);

    for (int i = 1; i <= n; i++)
    {
        bool is_lap = false;
        double cur_r = 1e9;

        if (used[i])
            continue;

        for (int j = 1; j <= n; j++)
        {
            if (i == j)
                continue;

            if (used[j])
            {
                double dis = calc_dis(x[i], y[i], x[j], y[j]);
                if (dis <= r[j])
                {
                    is_lap = true;
                    break;
                }

                cur_r = min(cur_r, dis - r[j]);
            }
        }

        if (is_lap)
            continue;

        cur_r = min(cur_r, min(min(xb - x[i], x[i] - xa), min(yb - y[i], y[i] - ya)));
        r[i] = cur_r;
        used[i] = true;
        dfs(cur + calc_area(cur_r));
        r[i] = 0;
        used[i] = false;
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    cin >> xa >> ya >> xb >> yb;

    double temp;
    temp = xa, xa = min(xa, xb), xb = max(temp, xb);
    temp = ya, ya = min(ya, yb), yb = max(temp, yb);

    for (int i = 1; i <= n; i++)
        cin >> x[i] >> y[i];

    ans = area = (xb - xa) * (yb - ya);

    dfs(0);

    cout << int(round(ans));

    return 0;
}

值得注意的是,roundround 函数的返回值为 doubledouble 类型,所以最后我们还需要进行 intint 的强制转换,不然的话有时会输出小数点,然后你会获得 80pts80pts 的好成绩。