HDU 5531 (平面几何 三分)

题意是给你n个点,然后要求以每个点为圆心画一个圆,使得两两相邻的圆心的圆相切,不相邻的圆可以相交,如果可以作这样的圆那么输出圆面积和的最小值和,每个圆的半径,否则输出IMPOSSIBLE。

首先我们求出每一条邻边的长度,记为l1, l2, l3, ... ,ln, 假定第一个圆的半径为x,那么我们可以用x表示所有圆的半径,分别为x, l1-x, l2-l1+x, l3-l2+l1-x .... 需要利用的就是第一个半径和最后一个半径的和必须等于ln(因为相切)。 发现最后一项+x还是-x和n的奇偶性有关,很明显需要分类。

若n是奇数:

那么最后一个圆的半径是l[n-1]-l[n-2] + l[n-3]-l[n-4] + ... + l[2]-l[1] + x, 它和第一个圆的半径x的和必须是l[n], 所以可以求出x,因为x的值必须是这个,所以只需要简单的判定是不是所有的半径都大于等于0,如果不是就无解,否则计算结果。

若n是偶数:

那么最后一个圆的半径是l[n-1]-l[n-2] + l[n-3]-l[n-4] + ... + l[3]-l[2] + l[1] - x, 它和第一个圆的半径x的和必须是ln[n] ,可以发现是一个常数方程,要么恒成立要么恒不成立。恒不成立显然就是无解的,如果恒成立,答案就是x^2 + (l[1]-x)^2 + (l[2]-l[1]+x)^2 + ... + (l[n-1]-l[n-2] + l[n-3]-l[n-4] + ... + l[3]-l[2] + l[1] - x)^2的结果乘以pi,这个式子展开是关于x的二次函数,可以通过三分求出极值,但是我们需要确定x的范围。显然x只需要满足任意一个圆的半径都非负就可以了,如果x的范围为空就无解,否则x的最优解就是区间三分找到的极值。

#include <bits/stdc++.h>
using namespace std;
#define maxn 11111
#define eps 1e-7
#define pi acos (-1)

struct point {
    double x, y;
}p[maxn];
double l[maxn]; //存第i条边到i+1条边的距离
double g[maxn]; //存l[n-1]-l[n-2]+l[n-3]-l[n-4]...
double ans[maxn]; //第i个圆的面积
int n;
double A, B, C; //二次函数的三个参数Ax^2+Bx+C

double dis (point a, point b) {
    double xx = a.x-b.x, yy = a.y-b.y;
    return sqrt (xx*xx + yy*yy);
}

double f (double x) {
    return A*x*x + B*x + C;
}

void work (double &L, double &R) {
    l[0] = l[n];
    L = 0, R = min (l[0], l[1]);
    for (int i = 1; i <= n; i++) {
        if (i&1) {
            L = max (L, -1*g[i]);
            R = min (R, min (l[i-1], l[i])-g[i]);
        }
        else {
            L = max (L, g[i]-min (l[i-1], l[i]));
            R = min (R, g[i]);
        }
    }
    return ;
}

void solve_even () { //n是偶数
    if (fabs (l[n]-g[n]) > eps) { //无解
        printf ("IMPOSSIBLE\n");
        return ;
    }
    A = n;
    B = C = 0.0;
    for (int i = 1; i <= n; i++) {
        int id = ((i&1)? 1 : -1); //-x还是+x
        B += 2.0*g[i]*id;
        C += g[i]*g[i];
    }
    double L, R, LL, RR;
    work (L, R); //找到x的范围
    if (L > R) {
        printf ("IMPOSSIBLE\n");
        return ;
    }
    while (R-L > eps) {
        LL = (L*2+R)/3, RR = (L+2*R)/3;
        double p1 = f (LL), p2 = f (RR);
        if (p1 > p2)
            L = LL;
        else
            R = RR;
    }
    double x = (L+R)/2.0; //第一个半径为x的时候总面积最小
    bool ok = 1;
    memset (ans, 0, sizeof ans);
    for (int i = 1; i <= n; i++) {
        int id = ((i&1)? 1 : -1); //+x还是-x
        double cur = g[i]+x*id; //当前半径
        if (cur >= 0) {
            ans[i] = cur;
        }
        else { //出现了负数
            ok = 0;
            break;
        }
    }
    if (!ok) {
        printf ("IMPOSSIBLE\n");
        return ;
    }
    double sum = 0.0;
    for (int i = 1; i <= n; i++) {
        sum += ans[i]*ans[i];
    }
    printf ("%.2f\n", sum*pi);
    for (int i = 1; i <= n; i++)
        printf ("%.2f\n", ans[i]);
    return ;
}

void solve_odd () { //n是奇数
    double x = (l[n]-g[n]) / 2.0;
    bool ok = 1;
    memset (ans, 0, sizeof ans);
    for (int i = 1; i <= n; i++) {
        int id = ((i&1)? 1 : -1); //+x还是-x
        double cur = g[i]+x*id; //当前半径
        if (cur >= 0) {
            ans[i] = cur;
        }
        else { //出现了负数
            ok = 0;
            break;
        }
    }
    if (!ok) {
        printf ("IMPOSSIBLE\n");
        return ;
    }
    double sum = 0.0;
    for (int i = 1; i <= n; i++) {
        sum += ans[i]*ans[i];
    }
    printf ("%.2f\n", sum*pi);
    for (int i = 1; i <= n; i++)
        printf ("%.2f\n", ans[i]);
    return ;
}

int main () {
    //freopen ("in", "r", stdin);
    int t;
    scanf ("%d", &t);
    while (t--) {
        scanf ("%d", &n);
        for (int i = 1; i <= n; i++) {
            scanf ("%lf%lf", &p[i].x, &p[i].y);
        }
        p[n+1] = p[1];
        for (int i = 1; i <= n; i++) {
            l[i] = dis (p[i], p[i+1]);
        }
        g[1] = 0;
        for (int i = 2; i <= n; i++) {
            g[i] = l[i-1]-g[i-1];
        }
        if (n&1)
            solve_odd ();
        else
            solve_even ();
    }
    return 0;
}


你可能感兴趣的:(HDU 5531 (平面几何 三分))