三分求极值
一元函数求单峰极值
用l,r来求到三等分点lsec,rsec,从而通过比较f(lsec),f(rsec)的大小,来确定极值的范围,进一步缩小区间
具体来说,如果f(lsec)<f(rsec),那么极值点必然是出现在[lsec,r]这一段区间内,同理可知大于的情况
double th_division(double left, double right, double eps)
{ //求解极小值点
double midl, midr;
while (right - left > eps)
{
midl = (2 * left + right) / 3;
midr = (left + 2 * right) / 3;
if (f(midl) <f(midr)) // f是需要求的函数
left = midl;
else
right = midr;
}
return midl; //返回近似极值点
}
这个可以优化到每次减少约二分之一的长度,就是将三等分点,改为中间附近某处取点
while (r - l > eps)
{
mid = (l + r) / 2;
double fl = f(mid - eps), fr = f(mid + eps);
if (fl < fr)
l = mid; // 这里不写成mid - eps,防止死循环;可能会错过极值,但在误差范围以内所以没关系
else
r = mid;
}
二元函数求极值
可以用三分套三分,也就是固定一个参数来改变另一个求到极值这样的思想,函数也得满足单峰的条件
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const double eps=1e-8;
double ans;
double f(double x,double y)
{
return (x-1)*(x-1)+(x+y)*(x+y)+sin(y);
}
double ff(double x)//固定x,三分y
{
double l=-4,r=4,mid;
while(r-l>eps)
{
mid=(l+r)/2;
if(f(x,mid-eps)>f(x,mid+eps))
l=mid;
else r=mid;
}
return f(x,mid);
}
int main()
{
double l=-4,r=4,mid;
while(r-l>eps)
{
mid=(l+r)/2;//改变x
if(ff(mid-eps)>ff(mid+eps))
{
l=mid;
}
else
{
r=mid;
}
}
cout<<ff(mid)<<endl;
}
参考:算法学习笔记(62): 三分法 - 知乎 (zhihu.com)
退火求极值
模拟退火有一定概率能够得到到最优解
算法模板,在主函数内迭代退火,具体退火算法是,用循环来模拟温度的下降,每一次生成一个随机数(可能与res相关),计算生成的和我们要保留迭代的所对应的函数值的大小,求出差值,如果要求最大值,如果生成的大于当前的,必然要迭代,但如果小于,为了避免局部最优解,也有一定几率会迭代
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <ctime>
#define x first
#define y second
using namespace std;
typedef pair<double, double> PDD;
const int N = 110;
int n;
PDD q[N];
double ans = 1e8;
double rand(double l, double r)
{
return (double)rand() / RAND_MAX * (r - l) + l;
}
double get_dist(PDD a, PDD b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
double calc(PDD p)
{
double res = 0;
for (int i = 0; i < n; i ++ )
res += get_dist(p, q[i]);
ans = min(ans, res);
return res;
}
void simulate_anneal()
{
PDD cur(rand(0, 10000), rand(0, 10000));
for (double t = 1e4; t > 1e-4; t *= 0.9)
{
PDD np(rand(cur.x - t, cur.x + t), rand(cur.y - t, cur.y + t));
double dt = calc(np) - calc(cur);
if (exp(-dt / t) > rand(0, 1)) cur = np;
}
}
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%lf%lf", &q[i].x, &q[i].y);
for (int i = 0; i < 100; i ++ ) simulate_anneal();
printf("%.0lf\n", ans);
return 0;
}
tip:dt=随机的-当前的
求最小值:if (exp(-dt / t) > rand(0, 1)) cur = np;
求最大值:if(exp(delta/t)>(double)rand()/RAND_MAX)
上面这道题目也可以用三分
#include <iostream>
#include <cmath>
using namespace std;
const int N = 110;
#define eps 1e-4
int n;
int x[N],y[N];
double dis(double x1,double y1)
{
double sum = 0.0;
for(int i =0;i < n;i ++ )
sum += sqrt((x[i] - x1)*(x[i] - x1) + (y[i] - y1)*(y[i] - y1));
return sum;
}
double check(double x1) // 三分y,x和y都是凹函数,结构一样
{
double l = 0.0,r = 10000.0;
while(r - l > eps)
{
double mid = (l + r) / 2;
double mmid = (mid + r) / 2;
if(dis(x1, mid) > dis(x1, mmid)) l = mid;
else r = mmid; // 这里是r = mmid;
}
return dis(x1,l);
}
int main()
{
cin >> n;
for(int i = 0;i < n;i ++ ) cin >> x[i] >> y[i];
double l = 0.0,r = 10000.0;
while(r - l > eps) // 三分x
{
double mid = (l + r) / 2;
double mmid = (mid + r) / 2;
if(check(mid) > check(mmid)) l = mid;
else r = mmid; // 这里是r = mmid;
}
printf("%d",(int)(check(l) + 0.5)); // 浮点数四舍五入
return 0;
}