二分推動算法

序:
二分搜索是很常見的一種算法,用來在有序序列中尋找某數(或者小於大於它的最*值的某數)。
二分答案也比較常見,估算出答案範圍後二分縮小範圍逼近答案
二分推動與二分答案很像,不一樣之處是二分推動並不是獲得答案的範圍,而是經過縮小答案對應的數據的範圍,逼近答案對應的數據(一般求最值)…ios

舉個例子:
平面上有n個點,已知每一個點的座標。求一個點m(x,y),使得m與其餘全部點的距離和最短。(x,y的精度爲0.1,最終的距離和 < 最小值+0.01便可)算法

思路一:暴力枚舉。
因爲精度是0.1,因此咱們能夠從minx -> maxx, miny -> maxy枚舉與全部點的距離,取最小。複雜度O(100n^2),n大一點即便忽略常數100(1/0.1*1/0.1)也得TLE。bash

思路二:二分推動。
盡然咱們想到了枚舉,並且咱們知道最小值的座標是惟一的。(不知道怎麼證,憑直覺這應該是顯然的…)
既然如此,假設這個點是m(x,y),對於m周邊的全部點必定距離和都要大於它的距離和(不然就不是最小了)。
還有一個隱含條件:minx<=x<=maxx; miny<=y<=maxy;
最重要的一點,越接近m的點的距離和應該越接近最小值(答案)。
知道範圍,又知道答案的特殊性與數據範圍的單調性,那麼就可使用二分推動算法逼近了markdown

思路是這樣的:
1.選取範圍內的任意一個點。
2.計算它的距離和以及它上下左右±r,也就是(x+r,y),(x,y+r)…四個座標的距離和,若是出現ans < minn,則至關於出現更優解,意味着答案應該更靠近那個點,因此咱們將當前點更新爲那個點繼續操做。
3.可能會出現上下左右4個點都比如今座標大,那看來是幅度太大了,r = r/2重複上一步操做。優化


僞代碼是這樣的:ui

while(步長大於偏差)
    {
        flag = true;
        while(flag)
        {
            先不容許下次以當前步長搜索
            for(四個方向)
            {
                計算當前座標的距離和;
                if(當前的答案更小)
                {
                    容許下次繼續以當前步長搜索
                    更新當前座標與最小答案
                }
            }
        }
        縮小步長
    }

最終的座標就是答案。spa


代碼實現:code

/* About: T1 二分推動 Auther: kongse_qi Date:2017/04/29 */

#include <cstdio>
#include <iostream> 
#include <cmath>
#define maxn 10005
#define INF 0x7fffff
#define eps 0.01
using namespace std;

int n, a[maxn][2];
double maxx, maxy, minx, miny;
double ans[2], cur_ans = INF;

void Read()
{
    double x, y;
    scanf("%d", &n);
    for(int i = 0; i != n; ++i)
    {
        scanf("%d%d", &a[i][0], &a[i][1]);
        x = double(a[i][0]);
        y = double(a[i][1]);
        maxx = max(maxx, x);
        minx = min(minx, x);
        maxy = max(maxy, y);
        miny = min(miny, y);
    }
    return ; 
}

double Cal(double x, double y)
{
    double ans = 0, x1, y1;
    if(x > maxx || y > maxy || x < minx || y < miny)    return cur_ans+1;
    for(int i = 0; i != n; ++i)
    {
        x1 = a[i][0];
        y1 = a[i][1];
        ans += sqrt((x-x1)*(x-x1)+(y-y1)*(y-y1));
    }
    return ans;
}

void Solve()
{
    int ti;
    double r = max(maxx-minx, maxy-miny);
    bool flag;
    double curx = (minx+maxx)/2, cury = (miny+maxy)/2;
    int dicx[] = {0, 1, 0, -1}, dicy[] = {1, 0, -1, 0};
    double ne_ans;
    cur_ans = Cal(curx, cury);
    while(r >= eps)
    {
        flag = true;
        while(flag)
        {
            flag = false;
            for(int i = 0; i != 4; ++i)
            {
                ne_ans = Cal(curx+r*dicx[i], cury+r*dicy[i]);
                if(ne_ans < cur_ans)
                {
                    flag = true;
                    curx += r*dicx[i];
                    cury += r*dicy[i];
                    cur_ans = ne_ans;
                }
            }
        }
        r *= 0.5;
    }
    ans[0] = curx;
    ans[1] = cury;
    return ;
}

int main()
{
    freopen("test.in", "r", stdin);
    Read();
    Solve();
    printf("%.1f %.1f", ans[0], ans[1]);
    return 0;
}

這與啓發式搜索很像,可是尚未搞懂怎個啓發式。做爲與二分答案法的姊妹算法,暴力代碼後也能夠看看可否經過二分優化。string

至此結束。
箜瑟_qi 2017.04.29 23:49it

相關文章
相關標籤/搜索