HDU 5531 Rebuild 相切的圆们

终于迎来了长春的重现。赛场上太多的遗憾,比赛的时候唯一看都没看一眼的题就是这个题,太可惜了。连同B题一起,为了抢个FB对不住打重现赛的童鞋们了。。。。。求放过,也不知道这个时候写题解算不算违规呢哈哈。。。。。。





题意:按顺序给出一个多边形,以多边形的每个顶点为圆心作圆,使得任意两相邻点对应的圆相切,求所有圆面积总和的最小值。




画个图算两下就出来了,发现只需要对n的奇偶性进行讨论即可。

设每个点为pi(0 <= i <= n - 1),对应圆的半径为ri,pi和p(i + 1)(默认pn为p0)的距离为di,则很显然我们得到了n个方程:

Fi:ri + r(i + 1) = di(0 <= i <= n - 1)。

通过这个方程我们发现,全部加起来除以2即得到了所有ri的和。当n为奇数时,将i为奇数的方程Fi相加可以得到r1 + r2 + ...... + r(n - 1)的和,再用总和减去它即得到了r0,也就是说,n为奇数时,这个方程组有唯一解,所以,解出所有的解,只要所有解都非负即可算出答案,如果有负的就IMPOSSIBLE。当n为偶数时,i为偶数的方程相加和i为奇数的方程相加的结果应该是一样的(都等于所有ri之和)。所以先算这两个和,如果这两个和不相等,也是IMPOSSIBLE。其次,我们从第二个方程起,可以将每个ri都变成与r0相关的式子,这样,所有圆的面积都可以用r0表示,最后的总面积是关于r0的二次函数,直接求解最小值即可。设ri = ai*r0 + bi,

则r(i + 1) = di - ri = -ai*r0 - bi + di。所以a(i + 1) = -ai,b(i + 1) = - bi + di。a0 = 1,b0 = 0。这样求出每个ai和bi(可以发现ai只有1和-1),然后面积和 = pi*sigma(ri^2,0 <= i <= n - 1) = A*r0^2 + B*r0 + C,算出A,B,C的值再根据每个ri的范围(0 <= ri <= min(di,d(i + 1))求出r0的取值范围(即维护区间的左右端点),如果区间右端点小于左端点,则无解IMPOSSIBLE。否则根据二次函数性质算出区间内最低点即可。






#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <stack>
#include <queue>
#include <set>
using namespace std;

const int MAX = 1e4 + 5;
const double pi = acos(-1.0);
const double eps = 1e-6;

int n, x[MAX], y[MAX];
double d[MAX], r[MAX];

int sgn(double t)
{
    return (t > eps) - (t < -eps);
}

double f(double a, double b, double c, double t)
{
    return a*t*t + b*t + c;
}

void input()
{
    scanf("%d", &n);
    for(int i = 0; i < n; i++)
        scanf("%d%d", &x[i], &y[i]);
}

void solve()
{
    x[n] = x[0];
    y[n] = y[0];
    for(int i = 0; i < n; i++)
        d[i] = sqrt((x[i + 1] - x[i])*(x[i + 1] - x[i]) + (y[i + 1] - y[i])*(y[i + 1] - y[i]));
    if(n&1)
    {
        double all = 0, other = 0, ans = 0;
        for(int i = 0; i < n; i++)
        {
            all += d[i];
            if(i&1)
                other += d[i];
        }
        r[0] = all/2 - other;
        if(sgn(r[0]) < 0)
        {
            printf("IMPOSSIBLE\n");
            return;
        }
        ans += r[0]*r[0];
        for(int i = 1; i < n; i++)
        {
            r[i] = d[i - 1] - r[i - 1];
            if(sgn(r[i]) < 0)
            {
                printf("IMPOSSIBLE\n");
                return;
            }
            ans += r[i]*r[i];
        }
        printf("%.2lf\n", pi*ans);
        for(int i = 0; i < n; i++)
            printf("%.2lf\n", r[i]);
    }
    else
    {
        double temp = 0;
        for(int i = 0; i < n; i += 2)
            temp += d[i] - d[i + 1];
        if(sgn(temp) != 0)
        {
            printf("IMPOSSIBLE\n");
            return;
        }
        int k = 1;
        double b = 0, L = 0, R = d[0];
        double A = 1, B = 0, C = 0;
        for(int i = 1; i < n; i++)
        {
            k = -k;
            b = -b + d[i - 1];
            if(k == 1)
                L = max(L, -b);
            else
                R = min(R, b);
            A++;
            B += 2*k*b;
            C += b*b;
        }
        if(sgn(L - R) > 0)
        {
            printf("IMPOSSIBLE\n");
            return;
        }
        double mid = -B/(2*A), ans;
        if(sgn(L - mid) >= 0)
        {
            ans = f(A, B, C, L);
            r[0] = L;
        }
        if(sgn(R - mid) <= 0)
        {
            ans = f(A, B, C, R);
            r[0] = R;
        }
        if(sgn(mid - L) >= 0 && sgn(mid - R) <= 0)
        {
            ans = f(A, B, C, mid);
            r[0] = mid;
        }
        printf("%.2lf\n", pi*ans);
        for(int i = 0; i < n; i++)
        {
            if(i)
                r[i] = d[i - 1] - r[i - 1];
            printf("%.2lf\n", r[i]);
        }
    }
}

int main()
{
    int T;
    scanf("%d", &T);
    for(int t = 1; t <= T; t++)
    {
        input();
        solve();
    }
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值