序:
二分搜索是很常见的一种算法,用来在有序序列中寻找某数(或者小于大于它的最*值的某数)。
二分答案也比较常见,估算出答案范围后二分缩小范围逼近答案。
二分推进与二分答案很像,不同之处是二分推进并非得到答案的范围,而是通过缩小答案对应的数据的范围,逼近答案对应的数据(通常求最值)…
举个例子:
平面上有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。
思路二:二分推进。
尽然我们想到了枚举,而且我们知道最小值的座标是唯一的。(不知道怎么证,凭直觉这应该是显然的…)
既然如此,假设这个点是m(x,y),对于m周边的所有点一定距离和都要大于它的距离和(否则就不是最小了)。
还有一个隐含条件:minx<=x<=maxx; miny<=y<=maxy;
最重要的一点,越接近m的点的距离和应该越接近最小值(答案)。
知道范围,又知道答案的特殊性与数据范围的单调性,那么就可以使用二分推进算法逼近了。
思路是这样的:
1.选取范围内的任意一个点。
2.计算它的距离和以及它上下左右±r,也就是(x+r,y),(x,y+r)…四个座标的距离和,如果出现ans < minn,则相当于出现更优解,意味着答案应该更靠近那个点,所以我们将当前点更新为那个点继续操作。
3.可能会出现上下左右4个点都比现在座标大,那看来是幅度太大了,r = r/2重复上一步操作。
伪代码是这样的:
while(步长大于误差)
{
flag = true;
while(flag)
{
先不允许下次以当前步长搜索
for(四个方向)
{
计算当前座标的距离和;
if(当前的答案更小)
{
允许下次继续以当前步长搜索
更新当前座标与最小答案
}
}
}
缩小步长
}
最终的座标就是答案。
代码实现:
/* 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;
}
这与启发式搜索很像,但是还没有搞懂怎个启发式。作为与二分答案法的姊妹算法,暴力代码后也可以看看能否通过二分优化。
至此结束。
箜瑟_qi 2017.04.29 23:49