题解-洛谷P2571 [SCOI2010]传送带
本文主要讲述了某蒟蒻历经一天一夜尝试
如有不足之处,或有更好的想法,望不吝赐教~
题目传送门
做法の尝试
推柿子
容易得到,最优路径一定是从
所花时间:
找到最优的
直接设坐标比较麻烦,可以设线段的比值「取
设
则:
同理:
这一大串式子看起来很难受,不妨简化一下。
设:
则
设:
则
c1*x+c2*y+c3*sqrt((c4*x+c5*y+c6)*(c4*x+c5*y+c6)+(c7*x+c8*y+c9)*(c7*x+c8*y+c9))
答案即为求解
对
令
将
这样一来,方程组的形式就优美了许多,我们可以轻松求解。
用正则表达式稍作整理:
塞到求解方程组的计算器中,即可求出

x=(c5*c9-c6*c8)/(c4*c8-c5*c7),y=(c6*c7-c4*c9)/(c4*c8-c5*c7)
将
上面两个式子的值都为
此时,
将
化简,得:
即
((c1*c5-c2*c4)*c9-c1*c6*c8+c2*c6*c7)/(c4*c8-c5*c7)
这就完了?
当然没有。。。
首先,你不知道解出来的
其次,就算是极小值,你也别忘了
再者,边界值也有可能是合法区间内的最值。
因此,可以先判断
code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<climits>
#define Min(a,b,c,d,e) (min(a,min(b,min(c,min(d,e)))))
using namespace std;
long double P,Q,R,ans=INT_MAX;
long double c1,c2,c3,c4,c5,c6,c7,c8,c9;
long double x,y;
struct node{
long double x,y;
}A,B,C,D,M,N;
inline long double dis(node a,node b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
inline long double f(long double x,long double y)
{
return c1*x+c2*y+c3*sqrt((c4*x+c5*y+c6)*(c4*x+c5*y+c6)+(c7*x+c8*y+c9)*(c7*x+c8*y+c9));
}
signed main()
{
scanf("%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf",&A.x,&A.y,&B.x,&B.y,&C.x,&C.y,&D.x,&D.y,&P,&Q,&R);
c1=dis(A,B)/P,c2=dis(C,D)/Q,c3=1/R;
c4=B.x-A.x,c5=D.x-C.x,c6=A.x-D.x,c7=B.y-A.y,c8=D.y-C.y,c9=A.y-D.y;
x=(c5*c9-c6*c8)/(c4*c8-c5*c7),y=(c6*c7-c4*c9)/(c4*c8-c5*c7);
if(x>=0&&x<=1&&y>=0&&y<=1) ans=min(ans,f(x,y));
ans=Min(f(0,0),f(0,1),f(1,0),f(1,1),ans);
printf("%.2Lf\n",ans);
return 0;
}
特判——「一元」
若只想到这里,你只能获得

还有一点需要处理:分母为
考虑
展开发现,此时
注意
特判这两种情况:
设
显然这是原先的一种特殊情况:
此时只需对原式略作修改即可。
所花时间:
容易发现,此时
重新用一下前面定义的几个参数:
答案即为求解
直接求导:
令
稍作化简:
不就是个一元二次方程嘛,手解也能解出来「就是懒得解」
再次用正则表达式整理:
塞进那个工具里,然后就得到了两个解:
x1=(sqrt(c3*c3*c7*c7+c3*c3*c4*c4-c1*c1)*(c1*c4*c9-c1*c6*c7)+((c1*c1-c3*c3*c4*c4)*c7-c3*c3*c7*c7*c7)*c9-c3*c3*c4*c6*c7*c7+(c1*c1*c4-c3*c3*c4*c4*c4)*c6)/(c3*c3*c7*c7*c7*c7+(2*c3*c3*c4*c4-c1*c1)*c7*c7+c3*c3*c4*c4*c4*c4-c1*c1*c4*c4)
x2=-(sqrt(c3*c3*c7*c7+c3*c3*c4*c4-c1*c1)*(c1*c4*c9-c1*c6*c7)+(c3*c3*c7*c7*c7+(c3*c3*c4*c4-c1*c1)*c7)*c9+c3*c3*c4*c6*c7*c7+(c3*c3*c4*c4*c4-c1*c1*c4)*c6)/(c3*c3*c7*c7*c7*c7+(2*c3*c3*c4*c4-c1*c1)*c7*c7+c3*c3*c4*c4*c4*c4-c1*c1*c4*c4)
同理,当
后面步骤相似,最后化简为:
y1=(sqrt(c3*c3*c8*c8+c3*c3*c5*c5-c2*c2)*(c2*c5*c9-c2*c6*c8)+((c2*c2-c3*c3*c5*c5)*c8-c3*c3*c8*c8*c8)*c9-c3*c3*c5*c6*c8*c8+(c2*c2*c5-c3*c3*c5*c5*c5)*c6)/(c3*c3*c8*c8*c8*c8+(2*c3*c3*c5*c5-c2*c2)*c8*c8+c3*c3*c5*c5*c5*c5-c2*c2*c5*c5)
y2=-(sqrt(c3*c3*c8*c8+c3*c3*c5*c5-c2*c2)*(c2*c5*c9-c2*c6*c8)+(c3*c3*c8*c8*c8+(c3*c3*c5*c5-c2*c2)*c8)*c9+c3*c3*c5*c6*c8*c8+(c3*c3*c5*c5*c5-c2*c2*c5)*c6)/(c3*c3*c8*c8*c8*c8+(2*c3*c3*c5*c5-c2*c2)*c8*c8+c3*c3*c5*c5*c5*c5-c2*c2*c5*c5)
至于
code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<climits>
#define Min(a,b,c,d,e) (min(a,min(b,min(c,min(d,e)))))
using namespace std;
long double P,Q,R,ans=INT_MAX;
long double c1,c2,c3,c4,c5,c6,c7,c8,c9;
long double x,y;
struct node{
long double x,y;
}A,B,C,D,M,N,t;
inline long double dis(node a,node b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
inline long double f(long double x,long double y)
{
return c1*x+c2*y+c3*sqrt((c4*x+c5*y+c6)*(c4*x+c5*y+c6)+(c7*x+c8*y+c9)*(c7*x+c8*y+c9));
}
signed main()
{
scanf("%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf",&A.x,&A.y,&B.x,&B.y,&C.x,&C.y,&D.x,&D.y,&P,&Q,&R);
c1=dis(A,B)/P,c2=dis(C,D)/Q,c3=1/R;
c4=B.x-A.x,c5=D.x-C.x,c6=A.x-D.x,c7=B.y-A.y,c8=D.y-C.y,c9=A.y-D.y;
if(c4*c8-c5*c7)
{
x=(c5*c9-c6*c8)/(c4*c8-c5*c7),y=(c6*c7-c4*c9)/(c4*c8-c5*c7);
if(x>=0&&x<=1&&y>=0&&y<=1) ans=min(ans,f(x,y));
}
else
{
if(C.x==D.x&&C.y==D.y)
{
x=(sqrt(c3*c3*c7*c7+c3*c3*c4*c4-c1*c1)*(c1*c4*c9-c1*c6*c7)+((c1*c1-c3*c3*c4*c4)*c7-c3*c3*c7*c7*c7)*c9-c3*c3*c4*c6*c7*c7+(c1*c1*c4-c3*c3*c4*c4*c4)*c6)/(c3*c3*c7*c7*c7*c7+(2*c3*c3*c4*c4-c1*c1)*c7*c7+c3*c3*c4*c4*c4*c4-c1*c1*c4*c4);
ans=min(ans,f(x,y));
x=-(sqrt(c3*c3*c7*c7+c3*c3*c4*c4-c1*c1)*(c1*c4*c9-c1*c6*c7)+(c3*c3*c7*c7*c7+(c3*c3*c4*c4-c1*c1)*c7)*c9+c3*c3*c4*c6*c7*c7+(c3*c3*c4*c4*c4-c1*c1*c4)*c6)/(c3*c3*c7*c7*c7*c7+(2*c3*c3*c4*c4-c1*c1)*c7*c7+c3*c3*c4*c4*c4*c4-c1*c1*c4*c4);
ans=min(ans,f(x,y));
}
else if(A.x==B.x&&A.y==B.y)
{
y=(sqrt(c3*c3*c8*c8+c3*c3*c5*c5-c2*c2)*(c2*c5*c9-c2*c6*c8)+((c2*c2-c3*c3*c5*c5)*c8-c3*c3*c8*c8*c8)*c9-c3*c3*c5*c6*c8*c8+(c2*c2*c5-c3*c3*c5*c5*c5)*c6)/(c3*c3*c8*c8*c8*c8+(2*c3*c3*c5*c5-c2*c2)*c8*c8+c3*c3*c5*c5*c5*c5-c2*c2*c5*c5);
ans=min(ans,f(x,y));
y=-(sqrt(c3*c3*c8*c8+c3*c3*c5*c5-c2*c2)*(c2*c5*c9-c2*c6*c8)+(c3*c3*c8*c8*c8+(c3*c3*c5*c5-c2*c2)*c8)*c9+c3*c3*c5*c6*c8*c8+(c3*c3*c5*c5*c5-c2*c2*c5)*c6)/(c3*c3*c8*c8*c8*c8+(2*c3*c3*c5*c5-c2*c2)*c8*c8+c3*c3*c5*c5*c5*c5-c2*c2*c5*c5);
ans=min(ans,f(x,y));
}
else if(C.x==D.x&&C.y==D.y&&A.x==B.x&&A.y==B.y) ans=min(ans,dis(A,D));
}
ans=Min(f(0,0),f(0,1),f(1,0),f(1,1),ans);
printf("%.2Lf\n",ans);
return 0;
}
特判——「平行」
若只想到这里,你还是只能获得

还有最后一种情况:即是
回到一开始的式子:
似乎很难下手了……
或许我们可以换一种思路「乱搞」:充分利用几何知识
两条直线平行,我们可以直接考虑以下几种方案:
- 过
向 作垂线,若与 有交点 ,则: - 过
向 作垂线,若与 有交点 ,则: - 过
向 作垂线,若与 有交点 ,则: - 过
向 作垂线,若与 有交点 ,则:
还有可能两条线段在同一条直线上,需要特判一下。
对这几种方案分别取最小值,得到正确答案的概率就大了许多。
code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<climits>
#define Min(a,b,c,d,e) (min(a,min(b,min(c,min(d,e)))))
using namespace std;
long double P,Q,R,ans=INT_MAX;
long double c1,c2,c3,c4,c5,c6,c7,c8,c9;
long double x,y;
struct node{
long double x,y;
}A,B,C,D,M,N,t;
inline long double dis(node a,node b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
inline long double f(long double x,long double y)
{
return c1*x+c2*y+c3*sqrt((c4*x+c5*y+c6)*(c4*x+c5*y+c6)+(c7*x+c8*y+c9)*(c7*x+c8*y+c9));
}
inline bool eq_direction(node A1,node B1,node A2,node B2)//判断共线向量A1B1、A2B2是否同向
{
if(B1.x-A1.x&&B2.x-A2.x) return (B1.x-A1.x)*(B2.x-A2.x)>0;
else return (B1.y-A1.y)*(B2.y-A2.y)>0;
}
inline node foot_point(node P,node A,node B)//过P点作直线AB的垂线,返回垂足
{
node res;
//计算AB的解析式
long double a=B.y-A.y,b=A.x-B.x,c=-a*A.x-b*A.y;
res.x=(b*b*P.x-a*b*P.y-a*c)/(a*a+b*b);
res.y=(a*a*P.y-a*b*P.x-b*c)/(a*a+b*b);
return res;
}
inline bool on_line(node P,node A,node B)//判断在直线AB上的点P是否在线段AB上,只需判断P是否在A、B围成的矩形中即可
{
return min(A.x,B.x)<=P.x&&P.x<=max(A.x,B.x)&&min(A.y,B.y)<=P.y&&P.y<=max(A.y,B.y);
}
signed main()
{
scanf("%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf",&A.x,&A.y,&B.x,&B.y,&C.x,&C.y,&D.x,&D.y,&P,&Q,&R);
c1=dis(A,B)/P,c2=dis(C,D)/Q,c3=1/R;
c4=B.x-A.x,c5=D.x-C.x,c6=A.x-D.x,c7=B.y-A.y,c8=D.y-C.y,c9=A.y-D.y;
if(c4*c8-c5*c7)
{
x=(c5*c9-c6*c8)/(c4*c8-c5*c7),y=(c6*c7-c4*c9)/(c4*c8-c5*c7);
if(x>=0&&x<=1&&y>=0&&y<=1) ans=min(ans,f(x,y));
}
else
{
if(C.x==D.x&&C.y==D.y)
{
x=(sqrt(c3*c3*c7*c7+c3*c3*c4*c4-c1*c1)*(c1*c4*c9-c1*c6*c7)+((c1*c1-c3*c3*c4*c4)*c7-c3*c3*c7*c7*c7)*c9-c3*c3*c4*c6*c7*c7+(c1*c1*c4-c3*c3*c4*c4*c4)*c6)/(c3*c3*c7*c7*c7*c7+(2*c3*c3*c4*c4-c1*c1)*c7*c7+c3*c3*c4*c4*c4*c4-c1*c1*c4*c4);
ans=min(ans,f(x,y));
x=-(sqrt(c3*c3*c7*c7+c3*c3*c4*c4-c1*c1)*(c1*c4*c9-c1*c6*c7)+(c3*c3*c7*c7*c7+(c3*c3*c4*c4-c1*c1)*c7)*c9+c3*c3*c4*c6*c7*c7+(c3*c3*c4*c4*c4-c1*c1*c4)*c6)/(c3*c3*c7*c7*c7*c7+(2*c3*c3*c4*c4-c1*c1)*c7*c7+c3*c3*c4*c4*c4*c4-c1*c1*c4*c4);
ans=min(ans,f(x,y));
}
else if(A.x==B.x&&A.y==B.y)
{
y=(sqrt(c3*c3*c8*c8+c3*c3*c5*c5-c2*c2)*(c2*c5*c9-c2*c6*c8)+((c2*c2-c3*c3*c5*c5)*c8-c3*c3*c8*c8*c8)*c9-c3*c3*c5*c6*c8*c8+(c2*c2*c5-c3*c3*c5*c5*c5)*c6)/(c3*c3*c8*c8*c8*c8+(2*c3*c3*c5*c5-c2*c2)*c8*c8+c3*c3*c5*c5*c5*c5-c2*c2*c5*c5);
ans=min(ans,f(x,y));
y=-(sqrt(c3*c3*c8*c8+c3*c3*c5*c5-c2*c2)*(c2*c5*c9-c2*c6*c8)+(c3*c3*c8*c8*c8+(c3*c3*c5*c5-c2*c2)*c8)*c9+c3*c3*c5*c6*c8*c8+(c3*c3*c5*c5*c5-c2*c2*c5)*c6)/(c3*c3*c8*c8*c8*c8+(2*c3*c3*c5*c5-c2*c2)*c8*c8+c3*c3*c5*c5*c5*c5-c2*c2*c5*c5);
ans=min(ans,f(x,y));
}
//else if(C.x==D.x&&C.y==D.y&&A.x==B.x&&A.y==B.y) ans=min(ans,dis(A,D));
else
{
ans=min(ans,dis(A,D)/R);
if(c4*(C.y-B.y)==c7*(C.x-B.x))//已知AB//CD,若AB//BC,则A、B、C、D四点共线
{
ans=min(ans,dis(A,B)/P+dis(B,C)/R+dis(C,D)/Q);//ABCD
ans=min(ans,dis(A,B)/P+dis(B,D)/R);//ABD
ans=min(ans,dis(A,C)/R+dis(C,D)/Q);//ACD
if(!eq_direction(A,C,B,C)&&!eq_direction(A,D,B,D))
{
if(eq_direction(A,C,C,D)) ans=min(ans,dis(A,C)/P+dis(C,D)/max(P,Q));//ACDB
else ans=min(ans,dis(A,D)/P);//ADCB
}
if(!eq_direction(A,C,A,D)&&!eq_direction(B,C,B,D))
{
if(eq_direction(C,A,A,B)) ans=min(ans,dis(A,B)/max(P,Q)+dis(B,D)/Q);//CABD
else ans=min(ans,dis(A,D)/Q);//CBAD
}
}
else//不共线
{
t=foot_point(C,A,B);
if(on_line(t,A,B)) ans=min(ans,dis(A,t)/P+dis(t,C)/R+dis(C,D)/Q);//AMCD
t=foot_point(D,A,B);
if(on_line(t,A,B)) ans=min(ans,dis(A,t)/P+dis(t,D)/R);//AMD
t=foot_point(B,C,D);
if(on_line(t,C,D)) ans=min(ans,dis(A,B)/P+dis(B,t)/R+dis(t,D)/Q);//ABND
t=foot_point(A,C,D);
if(on_line(t,C,D)) ans=min(ans,dis(A,t)/R+dis(t,D)/Q);//AND
}
}
}
ans=Min(f(0,0),f(0,1),f(1,0),f(1,1),ans);
printf("%.2Lf\n",ans);
return 0;
}
瓶颈
emmm...
仍是
乱搞终究不是正道。。。
不难发现,
若有大佬可以通过调整参数、用更好的方法求最值的方式来绕过这一特殊情况,请不吝赐教~
梯度下降法
再次回到起点:
非
在此介绍梯度下降法。
梯度
了解多元微积分的各位大佬们都知道,梯度是一个向量,指向多元函数的值变化最快的方向,大小即为变化率。
「不了解的可以看看 3Blue1Brown 的作者 Grant Sanderson 讲的多元微积分课程,在b站上已有熟肉【链接】」
百度百科对梯度的定义:
设二元函数
在平面区域 上具有一阶连续偏导数,则对于每一个点 都可定出一个向量 该
函数「也不是不对,但称为向量更好理解」就称为函数在点 的梯度,记作 或 。 于是有:
其中
称为(二维的)向量微分算子或Nabla算子,且有
梯度下降法的主要思想
再来介绍梯度下降法。
正如我们先前对
但是也正如我们遇到的问题,有时直接求解比较困难,我们不妨通过迭代,让梯度值逐渐下降,即逐渐接近极值点。
例如,对于函数

不妨设
梯度值
容易发现,在
而在
因此,我们有如下策略:
梯度值
即每次让
用数学语言来描述,即:
其中
优化——「学习率」
但这样就有一个问题:
例如
可以看到,虽然横坐标一直在变化,但一直在
为此,我们需要一个参数控制移动的距离,这个参数被称作学习率,用
显然,在刚刚的计算中,
当
而若将
降低的速度太慢,容易超时。
当
可以看到,右边的梯度值在每次迭代后都会下降,故称为梯度下降法「Gradient Descent」
只要选择合适的学习率,梯度就可以下降到任意小:
可以用泰勒公式进行严格证明,此处不再赘述(逃
因此可以通过梯度值的大小作为终止条件「也可以直接用迭代次数控制精度」
对于二元函数,同样可以用梯度下降法求解极值:
代码——「梯度下降法」
code:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
inline long double f(long double x)
{
return 3*x*x*x*x-x*x*x+2*x*x-9*x+5*sqrt((x+3)*(x+3)+(5*x+6)*(5*x+6))-25;
}
inline long double numerical_diff(long double x)//数值微分法估计一阶导数
{
long double dx=1e-6;
return (f(x+dx)-f(x-dx))/(dx*2);
}
inline long double gradient_descent(long double x,long double eta)//梯度下降法
{
long double eps=1e-6;
//int cnt=0;
while(abs(numerical_diff(x))>eps)
{
x-=eta*numerical_diff(x);
//cnt++;
}
//cout<<cnt<<endl;
return f(x);
}
signed main()
{
cout<<gradient_descent(0,0.02)<<endl;
return 0;
}

经过
优化——动量梯度下降法「MGD」
需要指出的是,梯度值为

「实际上,
同时,对于非单峰函数来说,梯度下降法的结果易受到初始值的影响,也就是陷入局部最优解。
现实生活中,一个小球从高处落下,大概率会越过比较低的坎继续下降。
我们同样可以引入惯性来优化:
用梯度模拟受力,使之不直接控制移动距离,而是给小球一个速度
于是,整个过程就像一个有动量的小球在空间中来回滚动,故称动量梯度下降法「Gradient Descent with Momentum,简称MGD」
根据动量定理
移项,得:
直接令
但在实际编程中,不必那么精细地刻画阻力,直接用一个速度衰减率
code:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
inline long double f(long double x)
{
return 3*x*x*x*x-x*x*x+2*x*x-9*x+5*sqrt((x+3)*(x+3)+(5*x+6)*(5*x+6))-25;
}
inline long double numerical_diff(long double x)
{
long double dx=1e-6;
return (f(x+dx)-f(x-dx))/(dx*2);
}
inline long double mgd(long double x,long double eta,long double beta)//动量梯度下降法
{
long double v=0,eps=1e-6;
int cnt=0;
while(abs(numerical_diff(x))>eps)
{
v=beta*v-eta*numerical_diff(x);
x+=v;
cnt++;
}
cout<<cnt<<endl;
return f(x);
}
signed main()
{
cout<<mgd(0,0.03,0.05)<<endl;
return 0;
}
迭代次数:
「多峰函数的优化效果较明显」
优化——自适应梯度算法「AdaGrad」
对比一下两种算法的真实移动距离
梯度下降法 | 动量梯度下降法 | |
---|---|---|
动量梯度下降法只优化了
很自然的想到,是否可以优化学习率
答案是肯定的,这种算法称为自适应梯度算法「AdaGrad【Adapt+Gradient】」,基本思想是开始时快速移动接近目标,然后减速提高精度「梯度对其产生的动力效果逐渐减弱」。实现时记录每次梯度下降的梯度平方和
code:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
inline long double f(long double x)
{
return 3*x*x*x*x-x*x*x+2*x*x-9*x+5*sqrt((x+3)*(x+3)+(5*x+6)*(5*x+6))-25;
}
inline long double numerical_diff(long double x)
{
long double dx=1e-6;
return (f(x+dx)-f(x-dx))/(dx*2);
}
inline long double AdaGrad(long double x,long double eta,long double beta)//自适应梯度算法
{
long double v=0,h=0,grad=numerical_diff(x),eps=1e-6;
//int cnt=0;
while(abs(grad)>eps)
{
h+=grad*grad;
v=beta*v-eta*grad/(sqrt(h)+eps);
x+=v;
grad=numerical_diff(x);
//cnt++;
}
//cout<<cnt<<endl;
return f(x);
}
signed main()
{
cout<<AdaGrad(0,0.5,0.05)<<endl;
return 0;
}
迭代次数:
「多峰函数的优化效果较明显」
优化——自适应动量算法「Adam」
自适应梯度算法的缺陷:若初始点离极值点较远,可能还没到极值点,小球就跑不动了「相应的有 RMSProp 优化算法,通过把
我们也可以将动量梯度下降法「MGD」与自适应梯度算法「AdaGrad」融合在一起,得到自适应动量算法「Adaptive Momentum Estimation,简称 Adam」
在梯度下降过程中,Adam 算法用梯度模拟小球受力改变速度
code:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
inline long double f(long double x)
{
return 3*x*x*x*x-x*x*x+2*x*x-9*x+5*sqrt((x+3)*(x+3)+(5*x+6)*(5*x+6))-25;
}
inline long double numerical_diff(long double x)
{
long double dx=1e-6;
return (f(x+dx)-f(x-dx))/(dx*2);
}
inline long double Adam(long double x,long double eta,long double beta1,long double beta2)
{
long double v=0,m=0,grad=numerical_diff(x),t,t1=beta1,t2=beta2,eps=1e-6;
//int cnt=0;
while(abs(grad)>eps)
{
t=eta*sqrt(1-t2)/(1-t1);
v+=(1-beta1)*(grad-v);
m+=(1-beta2)*(grad*grad-m);
x-=t*v/(sqrt(m)+eps);
t1*=beta1,t2*=beta2;
grad=numerical_diff(x);
//cnt++;
}
//cout<<cnt<<endl;
return f(x);
}
signed main()
{
cout<<Adam(0,0.5,0.6,0.9999)<<endl;
return 0;
}
迭代次数:
综合来看,AdaGrad 的算法似乎更具优势,但毕竟各个算法特点不同,适用的函数也不同,用哪种算法还是应根据实际应用而定。
二维写法
code:
struct node{
long double x,y;
};
inline long double f(long double x,long double y)
{
return ...;
}
inline long double part_x(long double x,long double y)
{
long double dx=1e-6;
return (f(x+dx,y)-f(x-dx,y))/(dx*2);
}
inline long double part_y(long double x,long double y)
{
long double dy=1e-6;
return (f(x,y+dy)-f(x,y-dy))/(dy*2);
}
inline node gd(long double x,long double y,long double eta,long double beta)
{
long double eps=1e-6;
node res;
int cnt=0,T=100;
while(T--&&abs(part_x(x,y))+abs(part_y(x,y))>eps)
{
x-=eta*part_x(x,y);
y-=eta*part_y(x,y);
//cout<<x<<' '<<y<<' '<<f(x,y)<<' '<<++cnt<<endl;
}
res.x=x,res.y=y;
return res;
}
inline node mgd(long double x,long double y,long double eta,long double beta)
{
long double vx=0,vy=0,eps=1e-6;
node res;
int cnt=0,T=100;
while(T--&&abs(part_x(x,y))+abs(part_y(x,y))>eps)
{
vx=beta*vx-eta*part_x(x,y);
vy=beta*vy-eta*part_y(x,y);
x+=vx,y+=vy;
//cout<<x<<' '<<y<<' '<<f(x,y)<<' '<<++cnt<<endl;
}
res.x=x,res.y=y;
return res;
}
inline node AdaGrad(long double x,long double y,long double eta,long double beta)
{
long double vx=0,vy=0,hx=0,hy=0,gradx=part_x(x,y),grady=part_y(x,y),grad=abs(gradx)+abs(grady),eps=1e-6;
node res;
int cnt=0,T=100;
while(T--&&grad>1e-5)
{
hx+=gradx*gradx;
hy+=grady*grady;
vx=beta*vx-eta*gradx/(sqrt(hx)+eps);
vy=beta*vy-eta*grady/(sqrt(hy)+eps);
x+=vx,y+=vy;
gradx=part_x(x,y);
grady=part_y(x,y);
grad=abs(gradx)+abs(grady);
//cout<<x<<' '<<y<<' '<<f(x,y)<<' '<<++cnt<<endl;
}
res.x=x,res.y=y;
return res;
}
inline node Adam(long double x,long double y,long double eta,long double beta1,long double beta2)
{
long double vx=0,vy=0,mx=0,my=0,gradx=part_x(x,y),grady=part_y(x,y),grad=abs(gradx)+abs(grady),t,t1=beta1,t2=beta2,eps=1e-7;
node res;
int cnt=0,T=100;
while(T--&&grad>1e-5)
{
t=eta*sqrt(1-t2)/(1-t1);
vx+=(1-beta1)*(gradx-vx);
vy+=(1-beta1)*(grady-vy);
mx+=(1-beta2)*(gradx*gradx-mx);
my+=(1-beta2)*(grady*grady-my);
x-=t*vx/(sqrt(mx)+eps);
y-=t*vy/(sqrt(my)+eps);
t1*=beta1,t2*=beta2;
gradx=part_x(x,y);
grady=part_y(x,y);
grad=abs(gradx)+abs(grady);
//cout<<x<<' '<<y<<' '<<f(x,y)<<' '<<++cnt<<endl;
}
res.x=x,res.y=y;
return res;
}
本题代码
回到此题,直接用 Adam 交了一发,AC~「可能运气较好……」

code:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<climits>
#include<cstdlib>
#include<ctime>
#define Min(a,b,c,d,e) (min(a,min(b,min(c,min(d,e)))))
using namespace std;
long double P,Q,R,ans=INT_MAX;
long double c1,c2,c3,c4,c5,c6,c7,c8,c9;
long double x,y;
struct node{
long double x,y;
}A,B,C,D,M,N,t;
inline long double dis(node a,node b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
inline long double f(long double x,long double y)
{
return c1*x+c2*y+c3*sqrt((c4*x+c5*y+c6)*(c4*x+c5*y+c6)+(c7*x+c8*y+c9)*(c7*x+c8*y+c9));
}
inline long double part_x(long double x,long double y)
{
long double dx=1e-6;
return (f(x+dx,y)-f(x-dx,y))/(dx*2);
}
inline long double part_y(long double x,long double y)
{
long double dy=1e-6;
return (f(x,y+dy)-f(x,y-dy))/(dy*2);
}
inline node Adam(long double x,long double y,long double eta,long double beta1,long double beta2)
{
long double vx=0,vy=0,mx=0,my=0,gradx=part_x(x,y),grady=part_y(x,y),grad=abs(gradx)+abs(grady),t,t1=beta1,t2=beta2,eps=1e-6;
node res;
int T=20;
while(T--&&grad>1e-4)
{
t=eta*sqrt(1-t2)/(1-t1);
vx+=(1-beta1)*(gradx-vx);
vy+=(1-beta1)*(grady-vy);
mx+=(1-beta2)*(gradx*gradx-mx);
my+=(1-beta2)*(grady*grady-my);
x-=t*vx/(sqrt(mx)+eps);
y-=t*vy/(sqrt(my)+eps);
t1*=beta1,t2*=beta2;
gradx=part_x(x,y);
grady=part_y(x,y);
grad=abs(gradx)+abs(grady);
}
res.x=x,res.y=y;
return res;
}
inline long double Rand()
{
return abs(rand()*rand()*rand()%(10000000)/(long double)(10000000));
}
inline void gradient()
{
for(int T=1;T<=200000;T++)
{
t=Adam(Rand(),Rand(),Rand(),Rand(),Rand());
if(t.x>=0&&t.x<=1&&t.y>=0&&t.y<=1) ans=min(ans,f(t.x,t.y));
}
}
signed main()
{
srand(time(0));
scanf("%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf",&A.x,&A.y,&B.x,&B.y,&C.x,&C.y,&D.x,&D.y,&P,&Q,&R);
c1=dis(A,B)/P,c2=dis(C,D)/Q,c3=1/R;
c4=B.x-A.x,c5=D.x-C.x,c6=A.x-D.x,c7=B.y-A.y,c8=D.y-C.y,c9=A.y-D.y;
gradient();
ans=Min(f(0,0),f(0,1),f(1,0),f(1,1),ans);
printf("%.2Lf\n",ans);
return 0;
}
三分套三分法
突然发现三分套三分跑的贼快,于是也打算简单实现一下。
模板就不讲了,直接贴一下之前的博客:「二分法&三分法模板」
上代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<climits>
#define Min(a,b,c,d,e) (min(a,min(b,min(c,min(d,e)))))
using namespace std;
long double P,Q,R,ans=INT_MAX,eps=1e-6;
long double c1,c2,c3,c4,c5,c6,c7,c8,c9;
long double x,y;
struct node{
long double x,y;
}A,B,C,D,M,N,t;
inline long double dis(node a,node b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
inline long double f(long double x,long double y)
{
return c1*x+c2*y+c3*sqrt((c4*x+c5*y+c6)*(c4*x+c5*y+c6)+(c7*x+c8*y+c9)*(c7*x+c8*y+c9));
}
inline long double g(long double x)
{
long double l=0,r=1,mid;
while(r-l>eps)
{
mid=(l+r)/2;
if(f(x,mid-eps)<f(x,mid+eps)) r=mid;
else l=mid;
}
return f(x,mid);
}
signed main()
{
scanf("%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf%Lf",&A.x,&A.y,&B.x,&B.y,&C.x,&C.y,&D.x,&D.y,&P,&Q,&R);
c1=dis(A,B)/P,c2=dis(C,D)/Q,c3=1/R;
c4=B.x-A.x,c5=D.x-C.x,c6=A.x-D.x,c7=B.y-A.y,c8=D.y-C.y,c9=A.y-D.y;
long double l=0,r=1,mid;
while(r-l>eps)
{
mid=(l+r)/2;
if(g(mid-eps)<g(mid+eps)) r=mid;
else l=mid;
}
printf("%.2Lf\n",g(mid));
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现