WHUT第十週訓練整理

WHUT第十週訓練整理

寫在前面的話:我的能力也有限,錯誤是在所難免的!因此如發現錯誤還請指出一同學習!

索引

(難度由題目自身難度與本週做題情況進行分類,僅供新生參考!)

零、基礎知識過關

一、easy:07、08、10、11、12、16、17

二、medium:01、09、15、19、21、22、23、24、25

三、hard:14、18、20、26

四、超綱:02、03、04、05、06、13

本題解報告大部分使用的是C++語言,在必要的地方使用C語言解釋。

零、基礎知識過關

圖論手開始學習計算幾何咯…這周與其說是計算幾何場,不如說是凸包場,然後還有好多超綱的題,不是我們現階段應該考慮的東西,所以這個報告就放出這些題的題解了。

下面一起學習一下計算幾何的基礎吧。

1. 精度問題

在計算幾何和其他需要用浮點數的問題中,一定要考慮精度的問題。

需要引入一個極小量 epseps,具體的數值可以根據題目而定,一般我設成 1e-9​ 差不多足夠了。

判斷兩個浮點數相同:fabs(ab)<epsfabs(a-b) < eps,而不是 a==ba == b

判斷大於關係:ab>epsa-b > eps,而不是 a>ba > b

判斷小於關係:ab<epsa-b < -eps,而不是 a<ba< b

輸出精度也需要注意,人爲的控制輸出的長度,後面的數據將會被四捨五入。

// c
scanf("%.4f", ans);
// c++
cout << fixed << setprecision(4) << ans << endl;

2. 向量

有大小有方向的量,又稱爲矢量。

二維的向量常用一個對數 (x,y)(x,y)​ 表示,代碼中常用一個結構體來實現向量。可以發現這樣的存儲跟二維平面中點的存儲方式是一致的,所以向量也可以用點的結構體保存。

struct vector// point
{
   double x,y;
   vector (double X=0,double Y=0)
   {
       x=X,y=Y;
    }
}

向量的模:即向量的長度。設向量 a=(x,y)\vec{a} = (x,y),則 a=x2+y2|\vec{a}| = \sqrt{x^2+y^2}

需要注意 (1)點 ±\pm 向量 == 點 (2)點 -== 向量

3. 極角

對於向量 a=(x,y)\vec{a} = (x,y)​,可以用函數 atan2(y,x)atan2(y,x)​ 來計算他的極角

按照極角爲關鍵字排序後的順序爲極角序。

4. 點積

ab\vec{a}·\vec{b}​ 的幾何意義爲 a\vec{a}​b\vec{b}​ 上的投影長度乘以 b\vec{b}​ 的模長

ab=abcosθ\vec{a}·\vec{b}=|\vec{a}||\vec{b}|cosθ​ ,其中 θθ​a,ba,b​ 之間的夾角。

座標表示

a=(x1,y1)  b=(x2,y2)\vec{a}=(x_1,y_1)~ ~\vec{b}=(x_2,y_2)​

ab=x1x2+y1y2\vec{a}·\vec{b}=x_1*x_2+y_1*y_2​

點積的應用

(1)判斷兩個向量是否垂直 ab\vec{a} \perp \vec{b} <=> ab=0\vec{a}·\vec{b}=0

(2)求兩個向量的夾角,點積 <0<0​ 爲鈍角,點積 >0>0​ 爲銳角

(3)求一個向量的模長,a=aa|\vec{a}| = \sqrt{\vec{a} \cdot \vec{a}}

5. 法向量

與單位向量垂直的向量稱爲單位法向量

6. 二維叉積

兩個向量的叉積是一個標量,a×b\vec{a} \times \vec{b}​ 的幾何意義爲他們所形成的平行四邊形的有向面積。

座標表示

a=(x1,y1)  b=(x2,y2)\vec{a}=(x_1,y_1)~ ~\vec{b}=(x_2,y_2)​

a×b=x1y2x2y1\vec{a} \times \vec{b}=x_1*y_2-x_2*y_1​

直觀理解,假如 b\vec{b}a\vec{a} 的左邊,則有向面積爲正,假如在右邊則爲負。假如 b,a\vec{b}, \vec{a} 共線,則叉積爲 00​

2~6 參考自

計算幾何總結 clover_hxy

https://blog.csdn.net/clover_hxy/article/details/53966405>

7. 全整數

在某些精度要求較高的題目中,可能可以使用 long double 類型給糊弄過去,實際上讓整個程序中出現的變量全爲整數,對精度控制是最好的。例如全轉爲向量表示,利用叉積等知識把原先需要用浮點類型的東西利用元組來唯一的表示出來。

8. 計算幾何相關算法

一維
  • 點的表示
二維
  • 線段(直線)的表示
  • 多邊形的表示
  • 圓的表示
  • 點線之間的關係(距離,是否在線上)
  • 點和多邊形的關係(是否在內部,多邊形內部(邊)點數量,各種性質的點)
  • 點和圓的關係(是否在內部,最小圓覆蓋,圓內(邊)點數量)
  • 線線之間的關係(位置關係,求交點,向量,夾角)
  • 線和多邊形的關係(位置關係,交點,平分或多分)
  • 線和圓的關係(位置關係,交點,各種圓的性質)
  • 多邊形之間的關係(位置關係,面積並,半平面交,旋轉卡殼,最大空凸包)
  • 圓圓之間的關係(位置關係,切線關係,各類多邊形外切圓內切圓,面積並,反演)
三維

二維問題都可以在三維中對應,具體性質都會發生變化。

推薦模板

kuangbin計算幾何板子

一、easy

1007:You can Solve a Geometry Problem too(線段相交)

題意:給出 NN 條線段的端點座標 (x1,y1,x2,y2)(x_1, y_1, x_2, y_2)​,現在問一共有多少個交點,重複的交點也需要多次計算。

範圍:1N1001 \le N \le 100xix_iyiy_i 是浮點數

分析: 判斷線段相交板子題。雙重循環調用函數判斷線段 ii 與線段 jj​ 是否相交,統計答案。

Code

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100 + 10;

// --------------以下均爲板子----------------------
struct Point
{ //點
    double x, y;
    Point() {}
    Point(int a, int b)
    { //方便賦值
        x = a;
        y = b;
    }
    void input()
    { //定義輸入函數方便用的時候
        scanf("%lf%lf", &x, &y);
    }
};

struct Line
{ //線段
    Point a, b;
    Line() {}
    Line(Point x, Point y)
    {
        a = x;
        b = y;
    }
    void input()
    {
        a.input();
        b.input();
    }
} line[MAXN];

bool judge(Point &a, Point &b, Point &c, Point &d)
{
    if (!(min(a.x, b.x) <= max(c.x, d.x) && min(c.y, d.y) <= max(a.y, b.y) && min(c.x, d.x) <= max(a.x, b.x) && min(a.y, b.y) <= max(c.y, d.y))) //這裏的確如此,這一步是判定兩矩形是否相交
        return false;
    double u, v, w, z; //分別記錄兩個向量
    u = (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y);
    v = (d.x - a.x) * (b.y - a.y) - (b.x - a.x) * (d.y - a.y);
    w = (a.x - c.x) * (d.y - c.y) - (d.x - c.x) * (a.y - c.y);
    z = (b.x - c.x) * (d.y - c.y) - (d.x - c.x) * (b.y - c.y);
    return (u * v <= 0.00000001 && w * z <= 0.00000001);
}
// --------------以上均爲板子----------------------

int main()
{
    int n;
    while (cin >> n, n)
    {
        for (int i = 0; i < n; i++)
        {
            line[i].input();
        }
        int ans = 0;
        // 雙重循環統計交點數量
        for (int i = 0; i < n; i++)
        {
            for (int j = i + 1; j < n; j++)
            {
                if (judge(line[i].a, line[i].b, line[j].a, line[j].b))
                    ans++;
            }
        }
        cout << ans << endl;
    }
    return 0;
}

1008:Pick-up sticks(暴力+判斷線段相交)

題意:按順序丟下 NN 根木棍,兩端點的座標爲 (xi,yi)(x_i, y_i)​,現在問有哪幾根木棍是沒有被壓住的。

範圍:1n1000001 \le n \le 100000xix_iyiy_i 是整數

分析:這個數據範圍真是嚇死人了,真要嚴格按照這個範圍的話感覺這題沒法做啊。這道題只要暴力枚舉每條邊後面的所有邊是否跟這條邊相交,有的話說明被覆蓋,沒有的話就輸出。注意還要輸出最後的一條邊,因爲必定是不會被覆蓋的。

Code

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e5 + 10;

// --------------以下均爲板子----------------------
struct Point
{ //點
    double x, y;
    Point() {}
    Point(int a, int b)
    { //方便賦值
        x = a;
        y = b;
    }
    void input()
    { //定義輸入函數方便用的時候
        scanf("%lf%lf", &x, &y);
    }
};

struct Line
{ //線段
    Point a, b;
    Line() {}
    Line(Point x, Point y)
    {
        a = x;
        b = y;
    }
    void input()
    {
        a.input();
        b.input();
    }
} line[MAXN];

bool judge(Point &a, Point &b, Point &c, Point &d)
{
    if (!(min(a.x, b.x) <= max(c.x, d.x) && min(c.y, d.y) <= max(a.y, b.y) && min(c.x, d.x) <= max(a.x, b.x) && min(a.y, b.y) <= max(c.y, d.y))) //這裏的確如此,這一步是判定兩矩形是否相交
        return false;
    double u, v, w, z; //分別記錄兩個向量
    u = (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y);
    v = (d.x - a.x) * (b.y - a.y) - (b.x - a.x) * (d.y - a.y);
    w = (a.x - c.x) * (d.y - c.y) - (d.x - c.x) * (a.y - c.y);
    z = (b.x - c.x) * (d.y - c.y) - (d.x - c.x) * (b.y - c.y);
    return (u * v <= 0.00000001 && w * z <= 0.00000001);
}
// --------------以上均爲板子----------------------

int main()
{
    int n;
    while (cin >> n, n)
    {
        for (int i = 0; i < n; i++)
        {
            line[i].input();  // 輸入線段
        }
        int first = 1;  // first控制格式
        cout << "Top sticks: ";
        // 暴力雙重循環枚舉檢查木棍i是否被壓住
        for (int i = 0; i < n; i++)
        {
            for (int j = i + 1; j < n; j++)
            {
                if (judge(line[i].a, line[i].b, line[j].a, line[j].b))
                {
                    break;
                }
                // 到這裏說明沒有木根壓着木棍i
                if (j == n - 1)
                {
                    if (first)
                        first = 0;
                    else
                        cout << ", ";
                    cout << i + 1;
                }
            }
        }
        cout << ", " << n << "." << endl;  // 別忘了輸出最後一根
    }
    return 0;
}

1010:Cupid’s Arrow(判斷點在多邊形內部)

題意:給出一個 NN 邊形,需要判斷 MM 個點 (xi,yi)(x_i, y_i) 是否在這個 NN​ 邊形內部(邊緣不算)。

範圍:2<N<100 , 0<xi,yi<10002 < N < 100~,~0 < x_i, y_i < 1000MM 的範圍未給出

分析:判斷點在多邊形內部板子題,點已經按照順時針排好序,直接調用函數判斷即可。唯一需要注意的就是在邊緣的點不算,要手動判斷一下。

Code

#include <bits/stdc++.h>
#define EPS 1e-8
using namespace std;
typedef long long LL;
const int N = 105;
struct point
{
    double x, y;
};
point poly[N];
int n, m;
double dabs(double a)
{
    return a < 0 ? -a : a;
}
double Min(double a, double b)
{
    return a < b ? a : b;
}
double Max(double a, double b)
{
    return a > b ? a : b;
}

bool on_line(point a, point b, point c) //判斷點在直線上
{
    if (c.x >= Min(a.x, b.x) && c.x <= Max(a.x, b.x) && c.y >= Min(a.y, b.y) && c.y <= Max(a.y, b.y))
    {
        return (dabs((c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y)) <= EPS);
    }
    return false;
}
bool inside_polygon(point p, int n) //判斷點在多邊形內
{
    int counter = 0;
    double xinter;
    point p1, p2;
    p1 = poly[0];
    for (int i = 1; i <= n; i++)
    {
        p2 = poly[i % n];
        if (on_line(p1, p2, p)) //此題在邊界不算
            return false;
        if (p.y > Min(p1.y, p2.y)) //如果射線交於邊的下端點,不算相交,所以是>
        {
            if (p.y <= Max(p1.y, p2.y))
            {
                if (p.x <= Max(p1.x, p2.x))
                {
                    if (p1.y != p2.y) //如果射線和邊重合,不算
                    {
                        xinter = (p.y - p2.y) * (p1.x - p2.x) / (p1.y - p2.y) + p2.x;
                        if (p1.x == p2.x || p.x <= xinter)
                            counter++;
                    }
                }
            }
        }
        p1 = p2;
    }
    if (counter % 2 == 0)
        return false;
    return true;
}
int main()
{
    while (cin >> n)
    {
        for (int i = 0; i < n; i++)
        {
            cin >> poly[i].x >> poly[i].y;
        }
        cin >> m;
        point p;
        for (int i = 0; i < m; i++)
        {
            cin >> p.x >> p.y;
            if (inside_polygon(p, n))
                cout << "Yes" << endl;
            else
                cout << "No" << endl;
        }
    }
    return 0;
}

1011:Shape of HDU(判斷凸多邊形)

題意:給一個有 NN 個頂點 (xi,yi)(x_i, y_i) 的多邊形,問是否是凸多邊形。

範圍:無明確指出,均爲整數

分析:凸包模板題。首先我們知道凸包一定是個凸多邊形,那麼我們只需要知道對這個多邊形求凸包後的點數是否跟原多邊形相等即可。

當然方法不止一種,可以參考:其他判斷多邊形的方法

Code

#include <bits/stdc++.h>
using namespace std;

const double eps = 1e-9;
const double PI = acos(-1.0);
const int MAXN = 1e5 + 10;

int n;

// --------------以下均爲板子----------------------
struct point
{
    double x, y;
} points[MAXN], ans[MAXN];

bool mult(point sp, point ep, point op)
{
    return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}

inline bool operator<(const point &l, const point &r)
{
    return l.y < r.y || (l.y == r.y && l.x < r.x);
}

int graham(point pnt[], int n, point res[])
{
    int i, len, top = 1;
    sort(pnt, pnt + n);
    if (n == 0)
    {
        return 0;
    }
    res[0] = pnt[0];
    if (n == 1)
    {
        return 1;
    }
    res[1] = pnt[1];
    if (n == 2)
    {
        return 2;
    }
    res[2] = pnt[2];
    for (i = 2; i < n; i++)
    {
        while (top && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    len = top;
    res[++top] = pnt[n - 2];
    for (i = n - 3; i >= 0; i--)
    {
        while (top != len && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    return top; //  返回凸包中點的個數
}
// --------------以上均爲板子----------------------

int main()
{
    while (cin >> n, n)
    {
        for (int i = 0; i < n; i++)
        {
            cin >> points[i].x >> points[i].y;
        }
        int num = graham(points, n, ans);
        if (num == n)
            cout << "convex" << endl;
        else
            cout << "concave" << endl;
    }
    return 0;
}

1012:Cupid’s Arrow(判斷點在多邊形內部)

又重題了喂!1010。

1016:改革春風吹滿地(多邊形面積)

題意:逆時針給 NN 個點 (xi,yi)(x_i, y_i),求這個多邊形的面積。

範圍:3N1003 \le N \le 100xix_iyiy_i 是整數

分析:求多邊形面積板子題。

Code

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100 + 10;

// --------------以下均爲板子----------------------
struct Lpoint
{
    double x, y;
} points[MAXN]; //  點

double area_of_polygon(int vcount, Lpoint plg[])
{
    int i;
    double s;
    if (vcount < 3)
    {
        return 0;
    }
    s = plg[0].y * (plg[vcount - 1].x - plg[1].x);
    for (i = 1; i < vcount; i++)
    {
        s += plg[i].y * (plg[(i - 1)].x - plg[(i + 1) % vcount].x);
    }
    return s / 2;
}
// --------------以上均爲板子----------------------

int main()
{
    int n;
    while (cin >> n, n)
    {
        for (int i = 0; i < n; i++)
        {
            cin >> points[i].x >> points[i].y;
        }
        cout << fixed << setprecision(1) << area_of_polygon(n, points) << endl;
    }
    return 0;
}

1017:Lifting the Stone(求多邊形重心)

題意:逆時針給 NN 個點 (xi,yi)(x_i, y_i),求這個多邊形的重心。

範圍:3N1000000 , xi,yi200003 \le N \le 1000000~,~|x_i|,|y_i| \le 20000

分析:多邊形重心板子題。

Code

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e6 + 10;

// --------------以下均爲板子----------------------
struct point
{
    double x, y;
} points[MAXN];

point bcenter(point pnt[], int n)
{
    point p, s;
    double tp, area = 0, tpx = 0, tpy = 0;
    p.x = pnt[0].x;
    p.y = pnt[0].y;
    for (int i = 1; i <= n; ++i)
    { //  point:0~n-1
        s.x = pnt[(i == n) ? 0 : i].x;
        s.y = pnt[(i == n) ? 0 : i].y;
        tp = (p.x * s.y - s.x * p.y);
        area += tp / 2;
        tpx += (p.x + s.x) * tp;
        tpy += (p.y + s.y) * tp;
        p.x = s.x;
        p.y = s.y;
    }
    s.x = tpx / (6 * area);
    s.y = tpy / (6 * area);
    return s;
}
// --------------以上均爲板子----------------------

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        int n;
        cin >> n;
        for (int i = 0; i < n; i++)
        {
            cin >> points[i].x >> points[i].y;
        }
        point ans = bcenter(points, n);
        cout << fixed << setprecision(2) << ans.x << " " << ans.y << endl;
    }
    return 0;
}

二、medium

1001:Everything Has Changed(餘弦定理,判斷兩個圓的關係)

題意:在原點有個半徑爲 RR 的大圓,還有 mm 個圓心位於 (xi,yi)(x_i, y_i) 且半徑爲 rir_i​ 的小圓,大圓與小圓重疊的部分會被切割掉,現在問大圓的周長是多少?

範圍:1m100 , 1000xi,yi1000 , 1R,ri10001 \le m \le 100~, ~ -1000 \le x_i, y_i \le 1000~, ~ 1 \le R, r_i \le 1000

分析:這道題看起來很複雜的樣子,但是實際上我們只需要求相交的弧長。分類討論大圓與各種小圓的關係:

① 小圓在大圓內部和外部(包括外切),不需要考慮,不會對大圓的周長造成影響。

② 小圓與大圓內切,此時只需要加上小圓的周長就可以了

③ 小圓與大圓相交

在這裏插入圖片描述

已知大圓的原周長爲 2πR2πR,而一個與之相交的小圓會讓周長變成 2πR+l1l22πR+l1-l2。且題目保證小圓不會出現相交,那麼每個小圓跟大圓的操作獨立開來,對會相交的小圓計算弧長更新答案即可。

那麼問題就轉換成求弧長,自然想到要求圓心角。

在這裏插入圖片描述

三邊都知道了,那麼這裏只需要用餘弦定理就可以求出圓心角。

詳見代碼。

Code

#include <bits/stdc++.h>
using namespace std;

const double eps = 1e-9;
const double PI = acos(-1.0);

int m, R;

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        cin >> m >> R;
        double ans = 2 * PI * R;  // 原周長
        for (int i = 0; i < m; i++)
        {
            double x, y, r;
            cin >> x >> y >> r;
            double len = sqrt(x * x + y * y);  // 圓心距
            // 情況1
            if (len >= R + r || len < R - r)
                continue;
            // 情況2
            if (len == R - r)
            {
                ans += 2 * PI * r;
                continue;
            }
            // 情況3,利用餘弦定理求出圓心角的一半再翻倍
            double angle1 = (R * R + len * len - r * r) / (2 * R * len);
            double angle2 = (r * r + len * len - R * R) / (2 * r * len);
            angle1 = 2 * acos(angle1);
            angle2 = 2 * acos(angle2);
            ans = ans - angle1 * R + angle2 * r;
        }
        cout << fixed << setprecision(7) << ans << endl;
    }
    return 0;
}

1009:Segment set(並查集+線段相交)

不說了,上一週並查集/最小生成樹場纔剛剛做過,有興趣的去看看上週的報告 1023。

https://blog.csdn.net/qq_41765114/article/details/104246134

Code


#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1000 + 10;

int n;

int fa[MAXN], num[MAXN];

struct Point
{ //點
    double x, y;
    Point() {}
    Point(int a, int b)
    { //方便賦值
        x = a;
        y = b;
    }
    void input()
    { //定義輸入函數方便用的時候
        scanf("%lf%lf", &x, &y);
    }
};

struct Line
{ //線段
    Point a, b;
    Line() {}
    Line(Point x, Point y)
    {
        a = x;
        b = y;
    }
    void input()
    {
        a.input();
        b.input();
    }
} line[MAXN];

// 判斷線段相交
bool judge(Point &a, Point &b, Point &c, Point &d)
{
    if (!(min(a.x, b.x) <= max(c.x, d.x) && min(c.y, d.y) <= max(a.y, b.y) && min(c.x, d.x) <= max(a.x, b.x) && min(a.y, b.y) <= max(c.y, d.y))) //這裏的確如此,這一步是判定兩矩形是否相交
        return false;
    double u, v, w, z; //分別記錄兩個向量
    u = (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y);
    v = (d.x - a.x) * (b.y - a.y) - (b.x - a.x) * (d.y - a.y);
    w = (a.x - c.x) * (d.y - c.y) - (d.x - c.x) * (a.y - c.y);
    z = (b.x - c.x) * (d.y - c.y) - (d.x - c.x) * (b.y - c.y);
    return (u * v <= 0.00000001 && w * z <= 0.00000001);
}

int find(int x)
{
    return x == fa[x] ? x : fa[x] = find(fa[x]);
}

void unin(int x, int y)
{
    int fx = find(x), fy = find(y);
    if (fx == fy)
        return;
    fa[fx] = fy;
    num[fy] += num[fx];
}

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        cin >> n;
        // 初始化
        for (int i = 1; i <= n; i++)
        {
            fa[i] = i;
            num[i] = 1;
        }
        int index = 1;
        for (int i = 0; i < n; i++)
        {
            char op;
            cin >> op;
            // 插入線段
            if (op == 'P')
            {
                line[index++].input(); // 輸入一條邊
                // 判斷是否與之間的線段相交
                for (int i = 1; i < index - 1; i++)
                {
                    if (judge(line[i].a, line[i].b, line[index - 1].a, line[index - 1].b))
                    {
                        unin(i, index - 1);
                    }
                }
            }
            // 查詢
            else
            {
                int x;
                cin >> x;
                int fx = find(x);
                cout << num[fx] << endl;
            }
        }
        if (T)
            cout << endl;
    }
    return 0;
}

1015:Fermat Point in Quadrangle(四邊形費馬點)

題意:給四邊形的四個點 (xi,yi)(x_i, y_i),求這個四邊形的費馬點。

範圍:0xi,yi10000 \le x_i, y_i \le 1000

分析:四邊形分爲凸四邊形與凹四邊形。

對於凸四邊形,顯然對角線上的點到兩個端點的距離之和最小,那麼兩條對角線的交點就滿足到四個端點的距離之和最小,因此凸四邊形的費馬點就是對角線的交點。

對於凹四邊形,費馬點一定就是那個凹進起來的點,如果選擇其他的點,那麼就會形成兩個三角形,不會是更優解。

在這裏插入圖片描述

例如,選擇凹進來的點的話答案是 a+b+ca+b+c,選擇其他點的答案是 A+B+C+DA+B+C+D,顯然 B+C>b+cB+C > b+cA+D>aA+D > a,後者被前者完爆,其他情況類似,可以自己手動推一下。

Code

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 8 + 10;
const int INF = 0x3f3f3f3f;

// --------------以下均爲板子----------------------
struct Point
{
    double x, y;
} points[MAXN];

double xmult(Point p1, Point p2, Point p0)
{
    return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}

// 求兩條線段的交點
Point intersection(Point p1, Point p2, Point p3, Point p4)
{
    Point ret = p1;
    double t = ((p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x)) / ((p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x));
    ret.x += (p2.x - p1.x) * t;
    ret.y += (p2.y - p1.y) * t;
    return ret;
}
// --------------以上均爲板子----------------------

double distance(Point a, Point b)
{
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

// 計算該點到四個頂點的距離之和
double len(Point x)
{
    double sum = 0;
    for (int i = 0; i < 4; i++)
    {
        sum += distance(x, points[i]);
    }
    return sum;
}

int main()
{
    while (cin >> points[0].x >> points[0].y)
    {
        for (int i = 1; i < 4; i++)
        {
            cin >> points[i].x >> points[i].y;
        }
        int num = 0;
        for (int i = 0; i < 4; i++)
        {
            if (points[i].x == -1 && points[i].y == -1)
                num++;
        }
        if (num == 4)
            break;
        double ans = INF;
        // 先計算選擇四個頂點的最優解
        for (int i = 0; i < 4; i++)
        {
            ans = min(ans, len(points[i]));
        }
        // 再計算對角線交點
        // 叉積的乘積結果相同說明順序正確
        // 點的順序爲0123
        if (xmult(points[0], points[1], points[3]) * xmult(points[1], points[2], points[3]) > 0)
        {
            ans = min(ans, len(intersection(points[0], points[2], points[1], points[3])));
        }
        // 點的順序爲0132
        else if (xmult(points[0], points[1], points[2]) * xmult(points[1], points[3], points[2]) > 0)
        {
            ans = min(ans, len(intersection(points[0], points[3], points[1], points[2])));
        }
        // 點的順序爲0213
        else
        {
            ans = min(ans, len(intersection(points[0], points[1], points[2], points[3])));
        }
        cout << fixed << setprecision(4) << ans << endl;
    }
    return 0;
}

1019:Quadrilateral(最大面積四邊形)

題意:給四邊形的四條邊 a,b,c,da, b, c, d,問這四條邊形成的最大面積四邊形的面積是多少,如果不能形成四邊形則輸出 1-1

範圍:1a,b,c,d10001 \le a, b, c, d \le 1000

分析:首先我們要知道構成四邊形的條件:最長邊 > 剩餘三邊之和

其次我們可以發現最大面積的四邊形一定是凸四邊形,如果是凹四邊形的話一定會造成面積的浪費,因此我們可以利用海倫公式求出凸四邊形的面積。

Code

#include <bits/stdc++.h>
using namespace std;

double arr[4];

int main()
{
    int T;
    cin >> T;
    int kase = 1;
    while (T--)
    {
        double a, b, c, d;
        cin >> arr[0] >> arr[1] >> arr[2] >> arr[3];
        sort(arr, arr + 4);
        cout << "Case " << kase++ << ": ";
        // 構成四邊形的條件
        if (arr[3] >= arr[0] + arr[1] + arr[2])
        {
            cout << "-1" << endl;
        }
        else
        {
            // 海倫公式
            double p = (arr[0] + arr[1] + arr[2] + arr[3]) / 2;
            double s = sqrt((p - arr[0]) * (p - arr[1]) * (p - arr[2]) * (p - arr[3]));
            cout << fixed << setprecision(6) << s << endl;
        }
    }
    return 0;
}

1021:Summer holiday(凸包)

題意:給 NN 個點 (xi,yi)(x_i, y_i),問選擇其中三個點形成的三角形的最大面積。

範圍:3N1e63 \le N \le 1e6xix_iyiy_i​ 是整數

分析:跟 1020 差不多的題目,稍微簡單一點,也是求出凸包之後三重循環枚舉凸包上面的點構成三角形統計答案即可。(聽說在隨機數據的情況下凸包上面的點的個數只有 log(n)log(n) 個)

Code

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e6 + 10;

// --------------以下均爲板子----------------------
struct point
{
    double x, y;
} p[MAXN], ans[MAXN], base;

bool mult(point sp, point ep, point op)
{
    return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}

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

inline bool operator<(const point &l, const point &r)
{
    return l.y < r.y || (l.y == r.y && l.x < r.x);
}

int cross(point sp, point ep, point op)
{
    return (sp.x - op.x) * (ep.y - op.y) - (ep.x - op.x) * (sp.y - op.y);
}

bool cmp(point a, point b)
{
    int ans = cross(a, b, base);
    if (ans == 0)
        return dis(base, a) > dis(base, b);
    else
        return ans > 0;
}

int graham(point pnt[], int n, point res[])
{
    int i, len, top = 1;
    sort(pnt, pnt + n);
    if (n == 0)
    {
        return 0;
    }
    res[0] = pnt[0];
    if (n == 1)
    {
        return 1;
    }
    res[1] = pnt[1];
    if (n == 2)
    {
        return 2;
    }
    res[2] = pnt[2];
    for (i = 2; i < n; i++)
    {
        while (top && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    len = top;
    res[++top] = pnt[n - 2];
    for (i = n - 3; i >= 0; i--)
    {
        while (top != len && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    return top; //  返回凸包中點的個數
}
// --------------以上均爲板子----------------------

int main()
{
    int n;
    while (cin >> n)
    {
        for (int i = 0; i < n; i++)
        {
            cin >> p[i].x >> p[i].y;
        }
        base = p[0];
        int pos = 0;
        for (int i = 1; i < n; i++)
        {
            if (p[i].y < base.y || (p[i].y == base.y && p[i].x < base.x))
            {
                base = p[i];
                pos = i;
            }
            swap(p[0], p[pos]);
        }
        sort(p + 1, p + n, cmp);
        int num = graham(p, n, ans);
        double res = 0;
        for (int i = 0; i < num; i++)
        {
            for (int j = i + 1; j < num; j++)
            {
                for (int k = j + 1; k < num; k++)
                {
                    res = max(res, 1.0 * cross(ans[i], ans[j], ans[k]));
                }
            }
        }
        cout << fixed << setprecision(2) << res / 2.0 << endl;
    }
    return 0;
}

1022:Surround the Trees(凸包周長)

題意:給 NN​ 個點 (xi,yi)(x_i, y_i)​,現在用一根繩子將所有的點捆綁在內,問繩子的最短長度是多少。

範圍:N100 , 0xi,yi32767N \le 100~, ~0 \le x_i, y_i \le 32767​

分析:顯然只需要考慮最外面點,所以求出凸包之後計算凸包的周長即可。唯一需要注意的就是多點共線的情況!此時只需要一條線!

Code

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e6 + 10;

// --------------以下均爲板子----------------------
struct point
{
    double x, y;
} p[MAXN], ans[MAXN], base;

bool mult(point sp, point ep, point op)
{
    return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}

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

inline bool operator<(const point &l, const point &r)
{
    return l.y < r.y || (l.y == r.y && l.x < r.x);
}

int graham(point pnt[], int n, point res[])
{
    int i, len, top = 1;
    sort(pnt, pnt + n);
    if (n == 0)
    {
        return 0;
    }
    res[0] = pnt[0];
    if (n == 1)
    {
        return 1;
    }
    res[1] = pnt[1];
    if (n == 2)
    {
        return 2;
    }
    res[2] = pnt[2];
    for (i = 2; i < n; i++)
    {
        while (top && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    len = top;
    res[++top] = pnt[n - 2];
    for (i = n - 3; i >= 0; i--)
    {
        while (top != len && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    return top; //  返回凸包中點的個數
}
// --------------以上均爲板子----------------------

int main()
{
    int n;
    while (cin >> n, n)
    {
        for (int i = 0; i < n; i++)
        {
            cin >> p[i].x >> p[i].y;
        }
        int num = graham(p, n, ans);
        // 多個點都在一條線上,所以外側只有兩個點
        if (num == 2)
        {
            cout << fixed << setprecision(2) << dis(ans[0], ans[1]) << endl;
            continue;
        }
        // 計算周長
        double res = 0;
        for (int i = 0; i < num; i++)
        {
            res += dis(ans[i], ans[(i + 1) % num]);
        }
        cout << fixed << setprecision(2) << res << endl;
    }
    return 0;
}

1023:Wall(凸包)

題意:順時針給 NN 個點 (xi,yi)(x_i, y_i),現在要用最短長度的繩子將這個多邊形包圍起來,要滿足所有位置繩子距離多邊形至少爲 LL

範圍:3N1000 , 1L1000 , 10000xi,yi100003 \le N \le 1000~, ~ 1 \le L \le 1000~,~-10000 \le x_i, y_i \le 10000​

分析:顯然我們只需要考慮凸包上的點,因爲我們需要花費最少,如果考慮內部的點,那麼花費必然會增加。因此我們只需要把最外側的點全部連起來,再這個新多邊形的外側 LL 的距離包一圈即可。

在這裏插入圖片描述

直線的部分合起來就是凸包的周長,而曲線的部分拼接在一起會發現正好形成了一個半徑爲 LL​ 的圓,因此我們只要計算凸包周長以及一個圓的周長即可。

Code

#include <bits/stdc++.h>
using namespace std;

const double PI = acos(-1.0);
const int MAXN = 1000 + 10;

// --------------以下均爲板子----------------------
struct point
{
    double x, y;
} p[MAXN], ans[MAXN], base;

bool mult(point sp, point ep, point op)
{
    return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}

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

inline bool operator<(const point &l, const point &r)
{
    return l.y < r.y || (l.y == r.y && l.x < r.x);
}

int cross(point sp, point ep, point op)
{
    return (sp.x - op.x) * (ep.y - op.y) - (ep.x - op.x) * (sp.y - op.y);
}

bool cmp(point a, point b)
{
    int ans = cross(a, b, base);
    if (ans == 0)
        return dis(base, a) > dis(base, b);
    else
        return ans > 0;
}

int graham(point pnt[], int n, point res[])
{
    int i, len, top = 1;
    sort(pnt, pnt + n);
    if (n == 0)
    {
        return 0;
    }
    res[0] = pnt[0];
    if (n == 1)
    {
        return 1;
    }
    res[1] = pnt[1];
    if (n == 2)
    {
        return 2;
    }
    res[2] = pnt[2];
    for (i = 2; i < n; i++)
    {
        while (top && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    len = top;
    res[++top] = pnt[n - 2];
    for (i = n - 3; i >= 0; i--)
    {
        while (top != len && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    return top; //  返回凸包中點的個數
}
// --------------以上均爲板子----------------------

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        int n, l;
        cin >> n >> l;
        // 這裏進行了逆時針排序,其實是不必要的
        for (int i = 0; i < n; i++)
        {
            cin >> p[i].x >> p[i].y;
        }
        base = p[0];
        int pos = 0;
        for (int i = 1; i < n; i++)
        {
            if (p[i].y < base.y || (p[i].y == base.y && p[i].x < base.x))
            {
                base = p[i];
                pos = i;
            }
            swap(p[0], p[pos]);
        }
        sort(p + 1, p + n, cmp);
        int num = graham(p, n, ans);
        // 計算凸包的周長以及該圓的周長
        double res = 2 * PI * l;
        for (int i = 0; i < num; i++)
        {
            res += dis(ans[i], ans[(i + 1) % num]);
        }
        cout << (int)(res + 0.5) << endl;
        if (T)
            cout << endl;
    }
    return 0;
}

1024:最大三角形(凸包)

題意:給 NN​ 個點 (xi,yi)(x_i, y_i)​,在這些點中尋找三個點構成面積最大的三角形,輸出面積。

範圍:3N50000 , 10000xi,yi100003 \le N \le 50000~, ~ -10000 \le x_i, y_i \le 10000

分析:按照題目說的來做就可以了,很簡單。求出凸包之後三重循環枚舉三個點利用叉積更新面積即可。

Code

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 5e4 + 10;

// --------------以下均爲板子----------------------
struct point
{
	double x, y;
} p[MAXN], ans[MAXN], base;

bool mult(point sp, point ep, point op)
{
	return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}

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

inline bool operator<(const point &l, const point &r)
{
	return l.y < r.y || (l.y == r.y && l.x < r.x);
}

int cross(point sp, point ep, point op)
{
	return (sp.x - op.x) * (ep.y - op.y) - (ep.x - op.x) * (sp.y - op.y);
}

bool cmp(point a, point b)
{
	int ans = cross(a, b, base);
	if (ans == 0)
		return dis(base, a) > dis(base, b);
	else
		return ans > 0;
}

int graham(point pnt[], int n, point res[])
{
	int i, len, top = 1;
	sort(pnt, pnt + n);
	if (n == 0)
	{
		return 0;
	}
	res[0] = pnt[0];
	if (n == 1)
	{
		return 1;
	}
	res[1] = pnt[1];
	if (n == 2)
	{
		return 2;
	}
	res[2] = pnt[2];
	for (i = 2; i < n; i++)
	{
		while (top && mult(pnt[i], res[top], res[top - 1]))
		{
			top--;
		}
		res[++top] = pnt[i];
	}
	len = top;
	res[++top] = pnt[n - 2];
	for (i = n - 3; i >= 0; i--)
	{
		while (top != len && mult(pnt[i], res[top], res[top - 1]))
		{
			top--;
		}
		res[++top] = pnt[i];
	}
	return top; //  返回凸包中點的個數
}
// --------------以上均爲板子----------------------

int main()
{
	int n;
	while (cin >> n)
	{
        // 逆時針排序
		for (int i = 0; i < n; i++)
		{
			cin >> p[i].x >> p[i].y;
		}
		base = p[0];
		int pos = 0;
		for (int i = 1; i < n; i++)
		{
			if (p[i].y < base.y || (p[i].y == base.y && p[i].x < base.x))
			{
				base = p[i];
				pos = i;
			}
			swap(p[0], p[pos]);
		}
		sort(p + 1, p + n, cmp);
		int num = graham(p, n, ans);
        // 三重循環枚舉三角形更新答案
		double S = 0;
		for (int i = 0; i < num; i++)
		{
			for (int j = i + 1; j < num; j++)
			{
				for (int k = j + 1; k < num; k++)
				{
					S = max(S, 1.0 * cross(ans[i], ans[j], ans[k]));
				}
			}
		}
		cout << fixed << setprecision(2) << S / 2.0 << endl;
	}
	return 0;
}

1025:Surround the Trees(凸包周長)

同 1022, 又重題了喂。

三、hard

1014:Watch out the Animal(凸包+模擬退火)

題意:動物園中有 NN 個休息點 (xi,yi)(x_i, y_i),每兩個休息點之間有一條監視線。動物有可能越獄,當動物越過最外側的監視線時視爲越獄成功。現在需要在動物園中選擇一個點建立信息中心,選擇與若干個休息點建立聯繫,建立聯繫的代價即兩者之間的距離。現在問如果在最低代價的條件下選擇一個點建立信息中心並建立聯繫,使得所有動物越獄成功的消息都可以被中心接收到。

範圍:5N100 , 0xi,yi100005 \le N \le 100~, ~ 0 \le x_i, y_i \le 10000​

分析:動物有可能從這個多邊形的任意一條邊越獄成功,所以我們需要知道所有的越獄消息,必定要讓信息中心與所有外側的點,即凸包上的點建立聯繫。而本題還有代價最小的條件,即信息中心到凸包上所有點的距離之和儘可能小,換言之題目要我們求的就是多邊形的費馬點。

對於三角形、四邊形的費馬點的求解是比較容易的,但是當推廣到 NN 邊形的時候,問題就變得複雜了起來。

這裏我們使用有點像搜索的算法——模擬退火,也叫隨機化貪心。

大意就是在多邊形內部隨機選擇一個點,每次以一個長度爲 stepstep 的步長向上下左右四個方向嘗試走一步,查看是否成爲更優解,是則更新答案。如果當前步長下已經不能得到更優解,那麼就縮短步長爲一半,知道精度滿足題目的要求。

其實也不是特別難,詳見代碼。

Code

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 100 + 10;

// --------------以下均爲板子----------------------
struct point
{
    double x, y;
} p[MAXN], ans[MAXN], base;

bool mult(point sp, point ep, point op)
{
    return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}

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

inline bool operator<(const point &l, const point &r)
{
    return l.y < r.y || (l.y == r.y && l.x < r.x);
}

int graham(point pnt[], int n, point res[])
{
    int i, len, top = 1;
    sort(pnt, pnt + n);
    if (n == 0)
    {
        return 0;
    }
    res[0] = pnt[0];
    if (n == 1)
    {
        return 1;
    }
    res[1] = pnt[1];
    if (n == 2)
    {
        return 2;
    }
    res[2] = pnt[2];
    for (i = 2; i < n; i++)
    {
        while (top && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    len = top;
    res[++top] = pnt[n - 2];
    for (i = n - 3; i >= 0; i--)
    {
        while (top != len && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    return top; //  返回凸包中點的個數
}
// --------------以上均爲板子----------------------

int num;

// 計算該點到凸包上所有點的距離之和
double calc(point a)
{
    double sum = 0;
    for (int i = 0; i < num; i++)
    {
        sum += dis(a, ans[i]);
    }
    return sum;
}

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        int n;
        cin >> n;
        for (int i = 0; i < n; i++)
        {
            cin >> p[i].x >> p[i].y;
        }
        num = graham(p, n, ans);
        // 設置凸包上第一個點爲初始點
        point now = ans[0];
        double res = calc(now), step = 100;
        // 本題的精度只要整數就可以,所以步長限制0.2足夠
        while (step > 0.2)
        {
            // 標記是否有更優解
            int update = 1;
            // 有更優解則持續進行
            while (update)
            {
                update = 0;
                point temp;
                double len;
                // 上
                temp = {now.x, now.y + step};
                len = calc(temp);
                if (len < res)
                {
                    res = len;
                    update = 1;
                    now = temp;
                }
                // 下
                temp = {now.x, now.y - step};
                len = calc(temp);
                if (len < res)
                {
                    res = len;
                    update = 1;
                    now = temp;
                }
                // 右
                temp = {now.x + step, now.y};
                len = calc(temp);
                if (len < res)
                {
                    res = len;
                    update = 1;
                    now = temp;
                }
                // 左
                temp = {now.x - step, now.y};
                len = calc(temp);
                if (len < res)
                {
                    res = len;
                    update = 1;
                    now = temp;
                }
            }
            // 當前步長下沒有更優解則更新步長
            step /= 2;
        }
        // 注意題目要求四捨五入
        cout << (int)(res + 0.5) << endl;
        if (T)
            cout << endl;
    }
    return 0;
}

1018:Rotational Painting(凸包+重心)

題意:給 NN​ 個點 (xi,yi)(x_i, y_i)​,現在將這個多邊形立起來放在桌上,問有多少種穩定的放置方案。

範圍:3N500003 \le N \le 50000xix_iyiy_i 都是浮點數

分析:首先我們需要知道能夠穩定放置的條件,就是多邊形的重心落在支撐線上。對於重心的求解直接套板子即可,至於支撐線,我們知道這個物體放置在桌上只可能是最外側的點與桌面進行接觸,因此支撐線就是外側點之間的連線,因此我們需要求出多邊形的凸包。

這樣我們就枚舉所有凸包上的線,判斷重心是否在線上(不包括端點),這樣就可以判斷以這條線作爲支撐線是否能夠穩定放置。

至於這道題爲什麼是 hard 呢,因爲除了上面的思路,本題很考驗細心與耐心,有一點點的小問題可能都要調上一整天,且不說 WA,可能連樣例都調不出來!(別問我是怎麼知道的)

Code

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
using namespace std;
const double eps = 1e-8;
struct point
{
    double x, y;
    point operator-(const point &t) const
    {
        point tmp;
        tmp.x = x - t.x;
        tmp.y = y - t.y;
        return tmp;
    }
    point operator+(const point &t) const
    {
        point tmp;
        tmp.x = x + t.x;
        tmp.y = y + t.y;
        return tmp;
    }
    bool operator==(const point &t) const
    {
        return fabs(x - t.x) < eps && fabs(y - t.y) < eps;
    }
} p[100010];
struct line
{
    point a, b;
};
int top;
bool cmpxy(point a, point b)
{
    if (a.y == b.y)
        return a.x < b.x;
    return a.y < b.y;
}
double cross(point a, point b, point c)
{
    return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}
void tubao(point *p, int n)
{
    if (n < 3)
        return;
    int i, m = 0;
    top = 1;
    sort(p, p + n, cmpxy);
    for (i = n; i < 2 * n - 1; i++)
        p[i] = p[2 * n - 2 - i];
    for (i = 2; i < 2 * n - 1; i++)
    {
        while (top > m && cross(p[top], p[i], p[top - 1]) < eps)
            top--;
        p[++top] = p[i];
        if (i == n - 1)
            m = top;
    }
}
point gravi(point *p, int n)
{
    int i;
    double A = 0, a;
    point t;
    t.x = t.y = 0;
    p[n] = p[0];
    for (i = 0; i < n; i++)
    {
        a = p[i].x * p[i + 1].y - p[i + 1].x * p[i].y;
        t.x += (p[i].x + p[i + 1].x) * a;
        t.y += (p[i].y + p[i + 1].y) * a;
        A += a;
    }
    t.x /= A * 3;
    t.y /= A * 3;
    return t;
}
point intersection(point u1, point u2, point v1, point v2)
{
    point ret = u1;
    double t = ((u1.x - v1.x) * (v1.y - v2.y) - (u1.y - v1.y) * (v1.x - v2.x)) / ((u1.x - u2.x) * (v1.y - v2.y) - (u1.y - u2.y) * (v1.x - v2.x));
    ret.x += (u2.x - u1.x) * t;
    ret.y += (u2.y - u1.y) * t;
    return ret;
}
point ptoline(point p, point l1, point l2)
{
    point t = p;
    t.x += l1.y - l2.y, t.y += l2.x - l1.x;
    return intersection(p, t, l1, l2);
}
bool dot_onseg(point p, point s, point e)
{
    if (p == s || p == e)
        return false;
    return cross(p, s, e) < eps &&
           (p.x - s.x) * (p.x - e.x) < eps && (p.y - s.y) * (p.y - e.y) < eps;
}
int main()
{
    int t, i, j, n;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d", &n);
        for (i = 0; i < n; i++)
            scanf("%lf%lf", &p[i].x, &p[i].y);
        point cen = gravi(p, n);
        tubao(p, n);
        line seg;
        point m;
        int count = 0;
        for (i = 0; i < top; i++)
        {
            m = ptoline(cen, p[i], p[i + 1]);
            if (dot_onseg(m, p[i], p[i + 1]))
                count++;
        }
        printf("%d\n", count);
    }
    return 0;
}

1020:Maple trees(凸包+最小圓覆蓋)

題意:給 NN​ 棵直徑爲 11​ 的樹位於 (xi,yi)(x_i, y_i)​,求把所有樹包含在其中的最小圓半徑是多少。

範圍:1N100 , 1000xi,yi10001 \le N \le 100~, ~ -1000 \le x_i, y_i \le 1000

分析:最開始的簡單想法就是求這個凸包的直徑,那麼直接上旋轉卡殼的板子就可以了,但是發現這個想法假了,凸包上的最小覆蓋圓直徑並不是凸包的直徑,而是有專門的最小圓覆蓋算法。

直接求解凸包上所有點的最小圓覆蓋不好算,可以把問題分解成小問題來解決。最小圓覆蓋算法也正是這麼做的,把凸包上所有點的最小覆蓋圓問題轉換成求解三角形的最小覆蓋圓問題。

凸包最小覆蓋圓可以由凸包上點所形成的三角形的最小覆蓋圓取最大值得到,因爲本道題目的數據範圍 NN 只有 100100,允許使用 O(n3)O(n^3) 的算法,因此我們可以三重循環枚舉凸包上的所有點構造三角形來求最小覆蓋圓。但是實際上最小覆蓋圓算法的時間複雜度的期望爲 O(n)O(n),有興趣的同學可以去了解一下。

那麼現在問題就轉化爲求解三角形的最小覆蓋圓。

分類討論:

在這裏插入圖片描述

① 銳角三角形

根據正弦定理,asinA=bsinB=csinC=2R\frac{a}{sinA} = \frac{b}{sinB} = \frac{c}{sinC} = 2R,且有面積公式 S=bcsinA2S = bcsin\frac{A}{2},聯立得到 R=abc4SR = \frac{abc}{4S}

abcabc 三邊都可以通過距離直接求出來,而面積 SS 可以利用叉積先求出平行四邊形的面積再除以二得到。

② 直角/鈍角三角形

此時三角形的最小覆蓋圓不是外接圓,而是以最長邊爲直徑的圓,此時 RR 爲最長邊的一半。

那麼樣子所有的問題都解決了,還要注意每棵樹都有自己的直徑,所有計算最後答案的時候需要加上 0.50.5,詳見代碼。

Code

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1000 + 10;

// --------------以下均爲板子----------------------
struct point
{
    double x, y;
} points[MAXN], ans[MAXN], base;

bool mult(point sp, point ep, point op)
{
    return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}

int cross(point sp, point ep, point op)
{
    return (sp.x - op.x) * (ep.y - op.y) - (ep.x - op.x) * (sp.y - op.y);
}

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

inline bool operator<(const point &l, const point &r)
{
    if (cross(l, r, base) == 0)
    {
        return dis(base, l) < dis(base, r);
    }
    else if (cross(l, r, base) > 0)
    {
        return true;
    }
    return false;
}

bool cmp(point l, point r)
{
    return l.y < r.y || (l.y == r.y && l.x < r.x);
}

int graham(point pnt[], int n, point res[])
{
    int i, len, top = 1;
    sort(pnt, pnt + n, cmp);
    if (n == 0)
    {
        return 0;
    }
    res[0] = pnt[0];
    if (n == 1)
    {
        return 1;
    }
    res[1] = pnt[1];
    if (n == 2)
    {
        return 2;
    }
    res[2] = pnt[2];
    for (i = 2; i < n; i++)
    {
        while (top && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    len = top;
    res[++top] = pnt[n - 2];
    for (i = n - 3; i >= 0; i--)
    {
        while (top != len && mult(pnt[i], res[top], res[top - 1]))
        {
            top--;
        }
        res[++top] = pnt[i];
    }
    return top; //  返回凸包中點的個數
}
// --------------以上均爲板子----------------------

// 判斷最長邊爲c的三角形是否爲鈍角三角形
int check(double a, double b, double c)
{
    return a * a + b * b < c * c;
}

// 計算三角形abc的最小覆蓋圓
double calc(point a, point b, point c)
{
    double p0p1 = dis(a, b);
    double p0p2 = dis(a, c);
    double p1p2 = dis(b, c);
    // 如果是直角/鈍角
    if (check(p0p1, p0p2, p1p2) || check(p0p1, p1p2, p0p2) || check(p1p2, p0p2, p0p1))
    {
        return max(p0p1, max(p0p2, p1p2)) / 2.0;
    }
    // 如果是銳角
    double S = fabs(mult(b, c, a)) / 2.0;
    return p0p1 * p0p2 * p1p2 / 4.0 / S;
}

int main()
{
    int n;
    while (cin >> n, n)
    {
        // 完成逆時針排序
        cin >> points[0].x >> points[0].y;
        base = points[0];
        int pos = 0;
        for (int i = 1; i < n; i++)
        {
            cin >> points[i].x >> points[i].y;
            if (points[i].y < base.y || (points[i].y == base.y && points[i].x < base.x))
            {
                base = points[i];
                pos = i;
            }
        }
        swap(points[0], points[pos]);
        sort(points + 1, points + n);
        // 需要特判一個點和兩個點的情況
        if (n == 1)
        {
            cout << "0.50" << endl;
            continue;
        }
        int num = graham(points, n, ans);
        if (num == 2)
        {
            double res = dis(ans[1], ans[0]) / 2.0 + 0.5;
            cout << fixed << setprecision(2) << res << endl;
            continue;
        }
        // 三重循環枚舉凸包上的所有三角形更新答案
        double res = 0;
        for (int i = 0; i < num; i++)
        {
            for (int j = i + 1; j < num; j++)
            {
                for (int k = j + 1; k < num; k++)
                {
                    res = max(res, calc(ans[i], ans[j], ans[k]));
                }
            }
        }
        cout << fixed << setprecision(2) << res + 0.5 << endl;
    }
    return 0;
}

1026:Quoit Design(二維平面最近點對,經典分治)

第四周的貪心場已經做過了,經典的分治問題。

有興趣的可以去看看我第四周的題解第 1015 題。

四、超綱

這些題除了 1013 都是2018CCPC網絡賽的題目,難度大,範圍廣,不是我們現在階段需要考慮的題目,這裏放出題解,有興趣的同學可以看看(這種難度的題目,什麼單調隊列優化DP,線段樹DP,網絡流…真的會有人看嗎?1013甚至都沒有題解!)

2018 CCPC 網絡賽題解

【END】感謝觀看

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章