题面
给出两个向量
a
⃗
=
(
x
0
,
y
0
)
\vec a=(x_0,y_0)
a=(x0,y0),
b
⃗
=
(
x
1
,
y
1
)
\vec b=(x_1,y_1)
b=(x1,y1),求整数
x
x
x,
y
y
y满足不同时为
0
0
0,使
∣
x
a
⃗
+
y
b
⃗
∣
|x\vec a + y \vec b|
∣xa+yb∣最小。
T
T
T组数据,每组数据输出
∣
x
a
⃗
+
y
b
⃗
∣
2
|x\vec a + y \vec b|^2
∣xa+yb∣2。
对于20%的数据点:
T
≤
100
T≤100
T≤100,存在一组最优的
(
x
,
y
)
(x,y)
(x,y),满足
∣
x
∣
,
∣
y
∣
≤
30
|x|,|y|≤30
∣x∣,∣y∣≤30。
对于60%的数据点:
T
≤
10000
T≤10000
T≤10000,存在一组最优的
(
x
,
y
)
(x,y)
(x,y),满足
min
{
∣
x
∣
,
∣
y
∣
}
≤
30
\min \{|x|,|y|\}≤30
min{∣x∣,∣y∣}≤30。
对于100%的数据点:
T
≤
100000
T≤100000
T≤100000,
∣
x
0
∣
,
∣
y
0
∣
,
∣
x
1
∣
,
∣
y
1
∣
≤
1
0
4
|x_0|,|y_0|,|x_1|,|y_1|≤10^4
∣x0∣,∣y0∣,∣x1∣,∣y1∣≤104,存在一组最优的
(
x
,
y
)
(x,y)
(x,y),满足
∣
x
∣
,
∣
y
∣
≤
1
0
4
|x|,|y|≤10^4
∣x∣,∣y∣≤104。
解法1
设
(
k
1
,
k
2
)
(k_1,k_2)
(k1,k2)为两整数。
我们的任务就是求
(
k
1
x
1
+
k
2
x
2
)
2
+
(
k
1
y
1
+
k
2
y
2
)
2
(k_1x_1+k_2x_2)^2+(k_1y_1+k_2y_2)^2
(k1x1+k2x2)2+(k1y1+k2y2)2的最小值。
展开得:
(
x
1
2
+
y
1
2
)
k
1
2
+
2
(
x
1
x
2
+
y
1
y
2
)
k
1
k
2
+
(
x
2
2
+
y
2
2
)
k
2
2
(x_1^2+y_1^2)k_1^2+2(x_1x_2+y_1y_2)k_1k_2+(x_2^2+y_2^2)k_2^2
(x12+y12)k12+2(x1x2+y1y2)k1k2+(x22+y22)k22
我们可以将式子看成一个以
k
1
k_1
k1为自变量的函数。
令
A
=
x
1
2
+
y
1
2
,
B
=
x
1
x
2
+
y
1
y
2
,
C
=
x
2
2
+
y
2
2
A=x_1^2+y_1^2,B=x_1x_2+y_1y_2,C=x_2^2+y_2^2
A=x12+y12,B=x1x2+y1y2,C=x22+y22
原式化为
A
×
k
1
2
+
2
B
×
k
2
×
k
1
+
C
×
k
2
2
A\times k_1^2+2B\times k_2\times k_1+C\times k_2^2
A×k12+2B×k2×k1+C×k22
根据二次函数顶点式得
k
1
=
−
B
A
k
2
k_1=-\frac{B}{A}k_2
k1=−ABk2时函数取到最小值。
那么我们就能够得到一个60分的算法了,每次枚举
k
1
k_1
k1或
k
2
k_2
k2解出与顶点最接近的另一个数即可。
考虑继续化简,由于实际的
k
1
k_1
k1与取到最小的
k
1
k_1
k1最多相差
1
2
\frac{1}{2}
21,我们可以尝试将
k
1
k_1
k1代入先消掉。
得:
(
C
−
B
2
A
)
k
2
2
(C-\frac{B^2}{A})k_2^2
(C−AB2)k22
代入A,B,C,得:
(
x
1
2
+
y
1
2
)
(
x
2
2
+
y
2
2
)
−
(
x
1
x
2
+
y
1
y
2
)
2
x
1
2
+
y
1
2
k
2
2
\frac{(x_1^2+y_1^2)(x_2^2+y_2^2)-(x_1x_2+y_1y_2)^2}{x_1^2+y_1^2}k_2^2
x12+y12(x12+y12)(x22+y22)−(x1x2+y1y2)2k22
拆项:
x
1
2
y
2
2
+
x
2
2
y
1
2
−
2
x
1
x
2
y
1
y
2
x
1
2
+
y
1
2
k
2
2
\frac{x_1^2y_2^2+x_2^2y_1^2-2x_1x_2y_1y_2}{x_1^2+y_1^2}k_2^2
x12+y12x12y22+x22y12−2x1x2y1y2k22
合并:
(
x
1
y
2
−
x
2
y
1
)
2
x
1
2
+
y
1
2
k
2
2
\frac{(x_1y_2-x_2y_1)^2}{x_1^2+y_1^2}k_2^2
x12+y12(x1y2−x2y1)2k22
可以发现这一定是一个非负数,而且仅仅只与
k
2
k_2
k2有关。
这样我们就可以将原式写成一个顶点式了:
A
(
k
1
+
B
A
k
2
)
2
+
(
x
1
y
2
−
x
2
y
1
)
2
x
1
2
+
y
1
2
k
2
2
A(k_1+\frac{B}{A}k_2)^2+\frac{(x_1y_2-x_2y_1)^2}{x_1^2+y_1^2}k_2^2
A(k1+ABk2)2+x12+y12(x1y2−x2y1)2k22
A
>
0
A>0
A>0,因此:
1
A
(
A
k
1
+
B
k
2
)
2
+
1
A
(
x
1
y
2
−
x
2
y
1
)
2
k
2
2
\frac{1}{A}(Ak_1+Bk_2)^2+\frac{1}{A}(x_1y_2-x_2y_1)^2k_2^2
A1(Ak1+Bk2)2+A1(x1y2−x2y1)2k22
至此,我们可以将式子划分为两部分,一部分只与
k
2
k_2
k2,一部分与
k
1
,
k
2
k_1,k_2
k1,k2之间的最小距离有关。
由于
k
1
,
k
2
k_1,k_2
k1,k2之间的最小距离在
1
2
\frac{1}{2}
21范围内,因此可以计算出第一个部分的范围。
根据前文有:
−
B
A
k
2
−
1
2
≤
k
1
≤
−
B
A
k
2
+
1
2
-\frac{B}{A}k_2-\frac{1}{2}≤k_1≤-\frac{B}{A}k_2+\frac{1}{2}
−ABk2−21≤k1≤−ABk2+21
−
1
2
A
≤
k
1
A
+
B
k
2
≤
1
2
A
-\frac{1}{2}A≤k_1A+Bk_2≤\frac{1}{2}A
−21A≤k1A+Bk2≤21A
因此:
(
k
1
A
+
B
k
2
)
2
≤
1
4
A
2
(k_1A+Bk_2)^2≤\frac{1}{4}A^2
(k1A+Bk2)2≤41A2
我们还发现当
k
2
>
1
,
(
x
1
y
2
−
x
2
y
1
)
2
>
0
k_2>1,(x_1y_2-x_2y_1)^2>0
k2>1,(x1y2−x2y1)2>0时,
(
x
1
y
2
−
x
2
y
1
)
2
k
2
2
(x_1y_2-x_2y_1)^2k_2^2
(x1y2−x2y1)2k22的增长速度实际上相当的快。
因此我们可以直接从
1
1
1开始枚举
k
2
k_2
k2,由于后面的部分一定是递增的,前面的部分有一定的范围,因此当后面的部分与
k
2
=
1
k_2=1
k2=1取到的值已经相差
1
4
A
2
\frac{1}{4}A^2
41A2时,我们就可以不再枚举了。
由于
1
4
A
2
≤
1
0
8
\frac{1}{4}A^2≤10^8
41A2≤108
因此这样做的时间复杂度为
O
(
T
C
)
O(T\sqrt C)
O(TC)的。
其中
C
C
C小于
1
0
8
10^8
108
解法2
见金斌的集训队论文《欧几里得算法的应用》-二维欧几里得算法
比第一种方法简单多了,虽然时间复杂度不明…
实现
这是解法2的实现,解法1的懒得写了。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define LL long long
void exgcd(LL x1,LL y1,LL x2,LL y2,LL &a,LL &b)
{
LL x=x1*x2+y1*y2,y=x1*x1+y1*y1,z=x2*x2+y2*y2;
if(x<0){
exgcd(-x1,-y1,x2,y2,a,b),a=-a;
return ;
}
if(4*x*x<y*z||x==0){
if(y>z)b=1;
else a=1;
return ;
}
bool F=0;
if(y<z)swap(x1,x2),swap(y1,y2),F=1;
y=x1*x1+y1*y1,z=x2*x2+y2*y2;
LL t=x/z,t1=t+1;
if(x-t*z<=t1*z-x&&t)
exgcd(x1-t*x2,y1-t*y2,x2,y2,a,b),b-=(t*a);
else exgcd(x1-t1*x2,y1-t1*y2,x2,y2,a,b),b-=(t1*a);
if(F)swap(a,b);
}
int main()
{
freopen("math.in","r",stdin);
freopen("math.out","w",stdout);
LL x1,x2,y1,y2;
while(~scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2))
{
LL a=0,b=0;
exgcd(x1,y1,x2,y2,a,b);
LL res1=(x1*a+x2*b)*(x1*a+x2*b),res2=(y1*a+y2*b)*(y1*a+y2*b);
printf("%lld\n",res1+res2);
}
}