1. 程式人生 > >★【模擬退火】Texas Trip

★【模擬退火】Texas Trip

Description
After a day trip with his friend Dick, Harry noticed a strange pattern of tiny holes in the door of his
SUV. The local American Tire store sells fiberglass patching material only in square sheets. What is the
smallest patch that Harry needs to fix his door?

Assume that the holes are points on the integer lattice in the plane. Your job is to find the area of the
smallest square that will cover all the holes.

Input
The first line of input contains a single integer T expressed in decimal with no leading zeroes, denoting
the number of test cases to follow. The subsequent lines of input describe the test cases.

Each test case begins with a single line, containing a single integer n expressed in decimal with no
leading zeroes, the number of points to follow; each of the following n lines contains two integers x
and y, both expressed in decimal with no leading zeroes, giving the coordinates of one of your points.

You are guaranteed that T ≤ 30 and that no data set contains more than 30 points. All points in each
data set will be no more than 500 units away from (0,0).

Output
Print, on a single line with two decimal places of precision, the area of the smallest square containing
all of your points.

Sample Input
2
4
-1 -1
1 -1
1 1
-1 1
4
10 1
10 -1
-10 1
-10 -1

Sample Output
4.00
242.00
找了半天,總算找到一個可讀性好一點的模擬退火了,雖然好像和正統的模擬退火不太一樣……
以下幾段引自百度百科。
模擬退火演算法是一種通用概率演算法。
模擬退火演算法可以分解為解空間、目標函式和初始解三部分。
模擬退火的基本思想:
(1) 初始化:初始溫度T0(充分大),初始解狀態S(是演算法迭代的起點),每個T值的迭代次數L
(2) 對k=1,……,L做第(3)至第6步:
(3) 產生新解S'
(4) 計算增量Δt'=C(S')-C(S),其中C(S)為評價函式
                                                      Δt'
(5) 若Δt'<0則接受S′作為新的當前解,否則以概率exp(- ———)接受S'作為新的當前解.
                                                       T
(6) 如果滿足終止條件則輸出當前解作為最優解,結束程式。(終止條件通常取為連續若干個新解都沒有被接受時終止演算法。)
(7) T逐漸減少,且T->0,然後轉第2步。


對於T的減少有如下幾種方式:
            T0
1. T = —————— ;
        lg(1 + t)
          T0
2. T = ———— ;
        1 + t
3. T = T0 ·a^t .
4. ……

模擬退火演算法的優化:
(1) 增加升溫或重升溫過程。在演算法程序的適當時機,將溫度適當提高,從而可啟用各狀態的接受概率,以調整搜尋程序中的當前狀態,避免演算法在區域性極小解處停滯不前。
(2) 增加記憶功能。為避免搜尋過程中由於執行概率接受環節而遺失當前遇到的最優解,可通過增加儲存環節,將“Best So Far”的狀態記憶下來。
(3) 增加補充搜尋過程。即在退火過程結束後,以搜尋到的最優解為初始狀態,再次執行模擬退火過程或區域性性搜尋。
(4) 對每一當前狀態,採用多次搜尋策略,以概率接受區域內的最優狀態,而非標準SA的單次比較方式。
(5) 結合其他搜尋機制的演算法,如遺傳演算法、混沌搜尋等。
(6)上述各方法的綜合應用。

對於這道題,正統方法不知,可模擬退火水過。

列舉旋轉角,逐步減小角度(每次讓其正切值減半)從而逼近最優解。
對於每一個旋轉角,設方向向量為(x, y),那麼就可以借用點乘的幾何意義將原點到每個點的有向線段全部投影到(x, y)上,再用類似的方法將與(x, y)垂直的向量(y, -x)也計算一遍,兩者取較大,就可以將結果求出來。

標準的旋轉變換:

程式中使用的旋轉變換:


並且其中將向量的模長設為了100以減小相對誤差。
Accode:

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
#include <cmath>
const int maxN = 40;
const double INF = 1e198, zero = 1e-12;
const int dx[] = {1, -1}, dy[] = {-1, 1};
int x[maxN], y[maxN], n, T;

double calc(double _x, double _y)
{
    double Min = INF, Max = -INF;
    for (int i = 0; i < n; ++i)
    {
        double tmp = _x * x[i] + _y * y[i];
        if (tmp > Max) Max = tmp;
        if (tmp < Min) Min = tmp;
    }
    double ans = Max - Min;
    Min = INF, Max = -INF;
    for (int i = 0; i < n; ++i)
    {
        double tmp = _y * x[i] - _x * y[i];
        if (tmp > Max) Max = tmp;
        if (tmp < Min) Min = tmp;
    }
    (Max - Min > ans) ? (ans = Max - Min) : ans;
    return ans * ans / (_x * _x + _y * _y);
//記得要除以模長,防止開方,直接返回求得正方形的面積。
}

double SAA()
{
    double sx = 100, sy = 0, T = 1, ans = calc(sx, sy);
    for (; T > zero; T /= 2)
    while (1)
    {
        bool ok = 0;
        for (int i = 0; i < 2; ++i)
        {
            double tx = sx + dx[i] * sy * T,
                   ty = sy + dy[i] * sx * T,
                   Now = calc(tx, ty),
                   tmp = sqrt(1e4 / (tx * tx + ty * ty));
            tx *= tmp, ty *= tmp;
	//保持模長不變。
	    if (Now < ans)
            {ans = Now; ok = 1; sx = tx, sy = ty;}
	//這裡可以只接受更優的解,因為當前決策
	//並不影響下一步是否能達到最優解。
        } //向順時針和逆時針兩個方向都要旋轉一遍。
        if (!ok) break;
    }
    return ans;
}

int main()
{
    freopen("Texas_Trip.in", "r", stdin);
    freopen("Texas_Trip.out", "w", stdout);
    for (scanf("%d", &T); T; --T)
    {
        scanf("%d", &n);
        for (int i = 0; i < n; ++i)
            scanf("%d%d", x + i, y + i);
        printf("%.2lf\n", SAA());
    }
    return 0;
}