Description
给出两个二维向量a和b,求两个整数 λ1,λ2 不同为0,使得 λ1a+λ2b 最小。
Solution
富榄教我学数学=w=
为了方便我们约定
|a|<|b|,a⋅b>0
如果不是的话可以通过调整
λ
的正负性和交换a,b来完成。
我们要让|ax+by|最小
那么同时也就是让|ax-by|最小
这样就可以用余弦定理了。。。
|ax−by|=|ax|2+|by|2−2|ax||by|cosθ−−−−−−−−−−−−−−−−−−−−−√
然后你需要知道几个结论:
1:当两个向量的夹角
θ>π3
,即
cosθ<12
时
原式
>=|ax|2+|by|2−|ax||by|−−−−−−−−−−−−−−−−−√
=(|ax|−|by|)2+|ax||by|−−−−−−−−−−−−−−−−−−√
如果当x,y同时大于0时原式 >=|a|
那么显然当y=0,x=1时原式最小。
2:向量a,b的答案和向量a,b+ka的答案是一致的。
因为|ax+by|=|a(x-ky)+(b+ka)y|
把右边看成向量a和b+ka的 λ 分别是(x-ky,y)
即向量(a,b)和向量(a,b+ka)的答案是一致的。
知道了这些,那么我们就有一个很直观的想法
每次通过选择合适的k,使得向量a,b之间的夹角增大。
那么我们可以通过以下方法来变换向量:
显然从b的结尾向a做垂直的角最大。
不行的话我们就想它离垂足越近越好。
也就是那个刚好包含垂足的向量的两端选一个角度最大的。
这样的复杂度看上去是log的,实际上。。。它应该就是log的=w=
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
typedef double db;
int ax,ay,bx,by;
ll solve(ll ax,ll ay,ll bx,ll by) {
ll d=ax*bx+ay*by;
if (!ax&&!ay) return 0;
if (d<0) return solve(ax,ay,-bx,-by);
db a=sqrt(ax*ax+ay*ay),b=sqrt(bx*bx+by*by);
ll lena=ax*ax+ay*ay,lenb=bx*bx+by*by;
db co=d*1.0/a/b;
if (co<0.5) return min(lena,lenb);
if (a>b) return solve(bx,by,ax,ay);
db len=d*1.0/a;
ll k=len/a;
if (len-k*a>(k+1)*a-len) k++;
return solve(ax,ay,bx-k*ax,by-k*ay);
}
int main() {
freopen("math.in","r",stdin);
freopen("math.out","w",stdout);
while (scanf("%d%d%d%d",&ax,&ay,&bx,&by)!=EOF)
printf("%lld\n",solve(ax,ay,bx,by));
}