社会我赞哥
说在前面
今天,也就是公元2019年6月28日,时隔两个月,多才多艺的yuzan1830又给本蒟蒻出新题了。(篇幅略长,慎入。)
他先在纸上画了一个半径为111的圆,圆心标为A1A_1A1。
然后他在⊙A1\odot A_1⊙A1上继续以111为半径接连作了六个圆,依次标为B1,B2,…,B6B_1,B_2,\dots,B_6B1,B2,…,B6。
他发现BBB这一层的圆出现了新的交点。于是他以这些交点为圆心继续画圆。
他发现CCC这一层的圆又出现了新的交点。于是他以这些交点为圆心继续画圆。
达羌:。。。你们自己理解就行了,我不画了!
直到某个时刻,他发现自己的纸已经画不下这么多圆了。
但他想象自己能从AAA一直画到ZZZ,甚至,从A1A_1A1开始向外画100001000010000层。
现在yuzan1830画了nnn层圆(不包括A1A_1A1)。然后他每次给出形如Na,MbN_a,M_bNa,Mb的两个点,问射线NaMbN_aM_bNaMb一共经过了纸上的多少个点。
例如n=2n=2n=2,射线C1B1C_1B_1C1B1经过了C1,B1,A1,B4,C7C_1,B_1,A_1,B_4,C_7C1,B1,A1,B4,C7五个点;n=3n=3n=3,射线D1B2D_1B_2D1B2经过了D1,B2,D8D_1,B_2,D_8D1,B2,D8三个点。
接受挑战
在yuzan1830的指引下,本蒟蒻勉强能够领会图中形成的规整的六边形的美。但是六边形太难了,本人思考了一下正方形的情况。
这么多点当然建系最优秀了。
达羌:。。。正方形还是比较好画的。
问题来了,为什么要把这个正方形斜过来?
因为这样的话属于同一层的点到原点的曼哈顿距离相等,便于点的名称和坐标之间的对应。
假设已知两点的坐标(x1,y1),(x2,y2)(x_1,y_1),(x_2,y_2)(x1,y1),(x2,y2),以图中D1(0,3),D5(2,−1)D_1(0,3),D_5(2,-1)D1(0,3),D5(2,−1)为例。
下面考虑求以这两点为端点的线段上的点的个数。
很明显这条线段上的点是等距的,则点的个数===线段长度÷\div÷相邻两点间的距离(下文简称为邻接距离)+1+1+1。
如上图,D1,D5D_1,D_5D1,D5的距离可表示为Δx+Δy=2+4\Delta x+\Delta y=2+4Δx+Δy=2+4。保持斜率k=ΔyΔx=42k=\frac{\Delta y}{\Delta x}=\frac 42k=ΔxΔy=24不变,将Δx,Δy\Delta x,\Delta yΔx,Δy同时减半,得到一个更短的距离1+21+21+2。这个距离实际上就是D1,C2D_1,C_2D1,C2或C2,D5C_2,D_5C2,D5的距离。
由于点的坐标全部是整数,故可以将Δx,Δy\Delta x,\Delta yΔx,Δy等比例缩小,当gcd(Δx,Δy)=1gcd(\Delta x,\Delta y)=1gcd(Δx,Δy)=1时,得到的距离Δx+Δy\Delta x+\Delta yΔx+Δy即为邻接距离。
问题来了,这个距离求的是线段上的点。求射线上的点怎么办?
延长线段就好了。只需求出终点的位置。可以根据{∣x∣+y⩽n∣x∣−y⩽n\begin{cases}|x|+y\leqslant n\\|x|-y\leqslant n\end{cases}{∣x∣+y⩽n∣x∣−y⩽n来计算。
最后一个问题,人家给的是Na,MbN_a,M_bNa,Mb,怎么把它转换成坐标?
不妨设NaN_aNa在第n0n_0n0层,则坐标(x,y)(x,y)(x,y)一定满足∣x∣+∣y∣=n0|x|+|y|=n_0∣x∣+∣y∣=n0。
考虑yyy轴正半轴与第一象限组成的区域,通过观察不难发现这个区域中第n0n_0n0层的点包括N1,N2,…,Nn0N_1,N_2,\dots,N_{n_0}N1,N2,…,Nn0,且横坐标顺时针递增。
同理,xxx轴正半轴与第四象限组成的区域中的点包括Nn0+1,Nn0+2,⋯ ,N2n0N_{n_0+1},N_{n_0+2},\cdots,N_{2n_0}Nn0+1,Nn0+2,⋯,N2n0,且横坐标顺时针递减。
yyy轴负半轴与第三象限组成的区域中的点包括N2n0+1,N2n0+2,⋯ ,N3n0N_{2n_0+1},N_{2n_0+2},\cdots,N_{3n_0}N2n0+1,N2n0+2,⋯,N3n0,且横坐标顺时针递减。
xxx轴负半轴与第二象限组成的区域中的点包括N3n0+1,N3n0+2,⋯ ,N4n0N_{3n_0+1},N_{3n_0+2},\cdots,N_{4n_0}N3n0+1,N3n0+2,⋯,N4n0,且横坐标顺时针递增。
于是通过推算得出结论:
Na{(a−1,n0−a+1)0<a⩽n0(2n0−a+1,n0−a+1)n0<a⩽2n0(2n0−a+1,−3n0+a−1)2n0<a⩽3n0(−4n0+a−1,−3n0+a−1)3n0<a⩽4n0N_a\begin{cases}
(a-1,n_0-a+1)&0<a\leqslant n_0\\
(2n_0-a+1,n_0-a+1)&n_0<a\leqslant 2n_0\\
(2n_0-a+1,-3n_0+a-1)&2n_0<a\leqslant 3n_0\\
(-4n_0+a-1,-3n_0+a-1)&3n_0<a\leqslant 4n_0
\end{cases}Na⎩⎪⎪⎪⎨⎪⎪⎪⎧(a−1,n0−a+1)(2n0−a+1,n0−a+1)(2n0−a+1,−3n0+a−1)(−4n0+a−1,−3n0+a−1)0<a⩽n0n0<a⩽2n02n0<a⩽3n03n0<a⩽4n0
至此,问题就解决了。
每次询问的时间复杂度为O(log2n)O(\log_2 n)O(log2n),因为求单位距离时用到了gcdgcdgcd,而其他部分的时间复杂度均为常数。
更多挑战
然而,问题真的解决了吗?
回到yuzan1830画的那张图:
看似美妙的正六边形,
达羌:。。。一点都不好玩。
因为有了许多60°60\degree60°,怎样建系都成了一个问题。极坐标系难以表示距离,笛卡尔坐标系又会算出一堆根式。yuzan1830不愧是神犇,甚至尝试用纯几何方法解决这个问题。
经过比较探讨,我们决定:暴力建系!
建出来就是这么丑,但是,没得法的鸭!
系建好了,然后依次解决之前的问题:
求坐标
yuzan1830:既然坐标系已经这么丑了,为什么不能再暴力一点!
我们以A1B1A_1B_1A1B1为基准线。对于点NaN_aNa,先找到N1N_1N1,然后绕原点顺时针旋转。先将aaa转掉nnn的整数倍,每一次转60°60\degree60°;对于剩余的amod  na\mod namodn计算出还需转动的角度和最终NaN_aNa与原点的距离。时间复杂度为常数。
以上图的C4C_4C4为例,首先不难得到∣C1A1∣=2|C_1A_1|=2∣C1A1∣=2,旋转角为120°120\degree120°。然后顺时针旋转60°60\degree60°到C3C_3C3位置,这样能保证到原点的距离不变。然后在△A1C3C4\triangle A_1C_3C_4△A1C3C4中解三角形,得到∣A1C4∣=3|A_1C_4|=\sqrt3∣A1C4∣=3,旋转角为30°30\degree30°,进而求出C4(32,32)C_4(\frac 32,\frac{\sqrt3}2)C4(23,23)。
一般地,设NaN_aNa位于第n0n_0n0层(n0>0n_0>0n0>0),则∣N1A1∣=n0|N_1A_1|=n_0∣N1A1∣=n0,旋转角永远为120°120\degree120°。
NaN_aNa到原点的距离∣NaA1∣=n02+(a−1mod  n0)2−2n0(a−1mod  n0)cos60°=r|N_aA_1|=\sqrt{n_0^2+(a-1\mod n_0)^2-2n_0(a-1\mod n_0)\cos 60\degree}=r∣NaA1∣=n02+(a−1modn0)2−2n0(a−1modn0)cos60°=r
NaN_aNa从N1N_1N1需旋转的角度∠N1A1Na=⌊a−1n0⌋⋅60°+arccosn0−(a−1mod  n0)cos60°n02+(a−1mod  n0)2−2n0(a−1mod  n0)cos60°=θ\angle N_1A_1N_a=\lfloor\frac{a-1}{n_0}\rfloor\cdot 60\degree+\arccos\frac{n_0-(a-1\mod n_0)\cos 60\degree}{\sqrt{n_0^2+(a-1\mod n_0)^2-2n_0(a-1\mod n_0)\cos 60\degree}}=\theta∠N1A1Na=⌊n0a−1⌋⋅60°+arccosn02+(a−1modn0)2−2n0(a−1modn0)cos60°n0−(a−1modn0)cos60°=θ
于是点NaN_aNa的坐标就出来了,很简单对不对?
Na(rcos(120°−θ),rsin(120°−θ))N_a(r\cos(120\degree-\theta),r\sin(120\degree-\theta))Na(rcos(120°−θ),rsin(120°−θ))
延长线段
跟正四边形的情况类似,即将给定线段从终点开始向外延伸邻接距离的整数倍,使之达到最长而又不越过第nnn层(第nnn层外面已经没有点了)。延长线段是用来求总距离的,故邻接距离可以在此之前计算好。
nnn层点中所有点的坐标一定满足以下条件:
{∣xtan60°−y∣⩽ntan60°∣xtan60°+y∣⩽ntan60°∣y∣⩽nsin60°\begin{cases}
|x\tan 60\degree-y|\leqslant n\tan 60\degree\\
|x\tan 60\degree+y|\leqslant n\tan 60\degree\\
|y|\leqslant n\sin 60\degree
\end{cases}⎩⎪⎨⎪⎧∣xtan60°−y∣⩽ntan60°∣xtan60°+y∣⩽ntan60°∣y∣⩽nsin60°
这个比较显然,其实就是第nnn层的点所在的六条直线的方程。
给定线段有起点Na(x1,y1)N_a(x_1,y_1)Na(x1,y1)和终点Mb(x2,y2)M_b(x_2,y_2)Mb(x2,y2),不妨把它看成一个向量NaMb→=(x2−x1,y2−y1)=r⃗\overrightarrow{N_aM_b}=(x_2-x_1,y_2-y_1)=\vec rNaMb=(x2−x1,y2−y1)=r,用坐标表示。
从该向量上截取一段与邻接距离等长的向量d⃗\vec dd(下文记为邻接向量)。要延长线段,就在原向量上加d⃗\vec dd就行了。
设给定线段向外延伸了kkk个邻接距离,那么延伸后的终点(x2+kxd⃗,y2+kyd⃗)(x_2+kx_{\vec d},y_2+ky_{\vec d})(x2+kxd,y2+kyd)一定落在六边形的范围内,即满足上述不等式组。解出最大的kkk即可。
求距离
整条线段的距离直接套距离公式就可以了。关键是如何求邻接距离。
回想一下,图形为正方形时,我们把两点的距离分成了Δx,Δy\Delta x,\Delta yΔx,Δy,然后对Δx,Δy\Delta x,\Delta yΔx,Δy进行“约分”。但是现在,六边形放在直角坐标系中显得不伦不类。
我们考虑两个单位向量e⃗1=(1,0),e⃗2=(cos60°,sin60°)\vec e_1=(1,0),\vec e_2=(\cos 60\degree,\sin 60\degree)e1=(1,0),e2=(cos60°,sin60°),与xxx轴正方向夹角分别为0°,60°0\degree,60\degree0°,60°,作为基底。根据平面向量基本定理,原向量r⃗\vec rr可以基于e⃗1,e⃗2\vec e_1,\vec e_2e1,e2,用另一整数对(p,q)(p,q)(p,q)来表示。
于是可以列出关于p,qp,qp,q的方程:
pe⃗1+qe⃗2=r⃗p\vec e_1+q\vec e_2=\vec rpe1+qe2=r
即
{p+qcos60°=x2−x1qsin60°=y2−y1\begin{cases}
p+q\cos 60\degree=x_2-x_1\\
q\sin 60\degree=y_2-y_1
\end{cases}{p+qcos60°=x2−x1qsin60°=y2−y1
解得
p=x2−x1−y2−y1tan60°,q=y2−y1sin60°p=x_2-x_1-\frac{y_2-y_1}{\tan 60\degree},q=\frac{y_2-y_1}{\sin 60\degree}p=x2−x1−tan60°y2−y1,q=sin60°y2−y1
然后仿照图形为四边形的做法,将p,qp,qp,q等比例缩小直至互质,求出邻接向量
d⃗=pgcd(p,q)e⃗1+qgcd(p,q)e⃗2=r⃗gcd(p,q)\vec d=\frac p{gcd(p,q)}\vec e_1+\frac q{gcd(p,q)}\vec e_2=\frac{\vec r}{gcd(p,q)}d=gcd(p,q)pe1+gcd(p,q)qe2=gcd(p,q)r
而邻接距离就是邻接向量的模长。
计算
同上。
ans=∣r⃗+kd⃗∣∣d⃗∣+1=gcd(p,q)+k+1ans=\frac{|\vec r+k\vec d|}{|\vec d|}+1=gcd(p,q)+k+1ans=∣d∣∣r+kd∣+1=gcd(p,q)+k+1
每次询问的时间复杂度依旧为O(log2n)O(\log_2 n)O(log2n)。
说在后面
其实正六边形的情况已经具有普遍性了。只需改变一下旋转角,理论上可以普及到任意正多边形。不过实现起来没有正六边形这么方便。果然正六边形还是最美的啊。
值得一提的是,前面正四边形的做法虽然有些投机取巧,但是整个计算过程中没有涉及浮点数。
yuzan1830正等待着本蒟蒻写出代码。他太天真了!
继续说在后面
2019年7月12日
后来还是花了点时间捣鼓出来了两段代码。
由于原题是正六边形,本人在处理正四边形的情况的时候只保证理论可行,而并没有仔细去考虑如何实现,毕竟问题的重心在正六边形的情况上。这导致一些具体过程在正六边形的情况中才得以铺开。关于正四边形的具体实现可以参考下面的代码。
另外正四边形的做法不涉及浮点数的说法也不准确,因为延长线段要解不等式。虽然可以用整除代替,但实现起来太麻烦。
说明一下输入格式:
第一行为一个整数nnn,表示在A1A_1A1的基础上向外画了nnn层圆。由于层数可达到100001000010000,无法用字母表示,故用数字表示层数,A1A_1A1位于第000层,B1B_1B1位于第111层,以此类推。
接下来若干行,每行中n1-a1 to n2-b2
表示询问射线PQPQPQ经过的点的个数,其中PPP是第n1n_1n1层第a1a_1a1个点,QQQ是第n2n_2n2层第a2a_2a2个点。输入保证合法。
正四边形
样例输入
3
3-1 to 3-5
3-1 to 2-2
2-7 to 2-4
1-3 to 1-1
样例输出
3
3
2
5
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int INF=0x7fffffff;
const double EPS=1e-6;
// point: 坐标系中的点
struct point{
int x,y;
point(int x=0,int y=0):x(x),y(y){}
// dist(a,b): 点a,b的曼哈顿距离
friend int dist(point a,point b){
return abs(a.x-b.x)+abs(a.y-b.y);
}
// convec(p1,p2): 与p1p2共线的邻接向量
friend point convec(point p1,point p2){
int dx=p2.x-p1.x,dy=p2.y-p1.y,gcd=__gcd(abs(dx),abs(dy));
return point(dx/gcd,dy/gcd);
}
};
// coord(n0,a): 第n0层第a个点的坐标
// coord(n0,a) = (a-1,n0-a+1) 0<a<=n0
// coord(n0,a) = (2n0-a+1,n0-a+1) n0<a<=2n0
// coord(n0,a) = (2n0-a+1,-3n0+a-1) 2n0<a<=3n0
// coord(n0,a) = (-4n0+a-1,-3n0+a-1) 3n0<a<=4n0
point coord(int n0,int a){
if(!n0)return point(0,0);
if(a<=n0)return point(a-1,n0-a+1);
if(a<=2*n0)return point(2*n0-a+1,n0-a+1);
if(a<=3*n0)return point(2*n0-a+1,-3*n0+a-1);
return point(-4*n0+a-1,-3*n0+a-1);
}
// extend(p1,p2,n): 延长p1p2使之不超过第n层,返回cnt
// d = convec(p1,p2)
// |p2.x+cnt*d.x|+(p2.y+cnt*d.y) <= n
// |p2.x+cnt*d.x|-(p2.y+cnt*d.y) <= n
int extend(point p1,point p2,int n){
point d=convec(p1,p2);
double t1=INF,b1=-INF,t2=INF,b2=-INF;
int cnt=INF;
if(d.x+d.y){
t1=(double)(n-p2.x-p2.y)/(d.x+d.y);
b1=(double)(-n-p2.x-p2.y)/(d.x+d.y);
}
if(d.x-d.y){
t2=(double)(n-p2.x+p2.y)/(d.x-d.y);
b2=(double)(-n-p2.x+p2.y)/(d.x-d.y);
}
if(d.x+d.y<0)swap(b1,t1);
if(d.x-d.y<0)swap(b2,t2);
if(b1-EPS<t1)cnt=min(cnt,(int)floor(t1+EPS));
if(b2-EPS<t2)cnt=min(cnt,(int)floor(t2+EPS));
return max(cnt,0);
}
// ans = gcd(p1p2.x,p1p2.y)+cnt+1
int main(){
int n,n1,a1,n2,a2;
scanf("%d",&n);
while(scanf("%d-%d to %d-%d",&n1,&a1,&n2,&a2)==4){
point p1=coord(n1,a1),p2=coord(n2,a2);
printf("%d\n",__gcd(abs(p1.x-p2.x),abs(p1.y-p2.y))+extend(p1,p2,n)+1);
}
return 0;
}
正六边形
样例输入
3
3-1 to 1-2
0-1 to 2-5
2-2 to 2-3
样例输出
3
4
3
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int INF=0x7fffffff;
const double EPS=1e-6;
const double PI=acos(-1);
const double sin60=sin(PI/3);
const double cos60=cos(PI/3);
const double tan60=tan(PI/3);
// double_to_int: 万恶的浮点数误差
int double_to_int(double x){
if(x>0)x+=EPS;else x-=EPS;
return (int)x;
}
// point: 坐标系中的点
struct point{
double x,y;
point(double x=0,double y=0):x(x),y(y){}
// dist(a,b): 点a,b的欧几里得距离
friend double dist(point a,point b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
// convec(p1,p2): 与p1p2同向的邻接向量
// p+q*cos60 = p2.x-p1.x
// q*sin60 = p2.y-p1.y
// d = (p2-p1)/gcd(p,q)
friend point convec(point p1,point p2){
int p=double_to_int(p2.x-p1.x-(p2.y-p1.y)/tan60);
int q=double_to_int((p2.y-p1.y)/sin60);
int gcd=__gcd(abs(p),abs(q));
return point((p2.x-p1.x)/gcd,(p2.y-p1.y)/gcd);
}
};
// coord(n0,a): 第n0层第a个点的坐标
// r^2 = n0^2+(a-1 mod n0)^2-2*n0(a-1 mod n)cos60
// theta = [(a-1)/n0](PI/3)+acos((n0-(a-1 mod n0)cos60)/r)
// coord(n0,a) = (r*cos(PI*2/3-theta),r*sin(PI*2/3-theta))
point coord(int n0,int a){
if(!n0)return point(0,0);
int left=(a-1)%n0;
double r=sqrt(n0*n0+left*left-2*n0*left*cos60);
double theta=floor((a-1)/n0)*(PI/3)+acos((n0-left*cos60)/r);
return point(r*cos(PI*2/3-theta),r*sin(PI*2/3-theta));
}
// extend(p1,p2,n): 延长p1p2使之不超过第n层,返回cnt
// d = convec(p1,p2)
// |(p2.x+cnt*d.x)tan60-(p2.y+cnt*d.y)| <= n*tan60
// |(p2.x+cnt*d.x)tan60+(p2.y+cnt*d.y)| <= n*tan60
// |p2.y+cnt*d.y| <= n*sin60
int extend(point p1,point p2,int n){
point d=convec(p1,p2);
double t1=INF,b1=-INF,t2=INF,b2=-INF,t3=INF,b3=-INF;
int cnt=INF;
if(fabs(d.x*tan60-d.y)>EPS){
t1=(n*tan60-p2.x*tan60+p2.y)/(d.x*tan60-d.y);
b1=(-n*tan60-p2.x*tan60+p2.y)/(d.x*tan60-d.y);
}
if(fabs(d.x*tan60+d.y)>EPS){
t2=(n*tan60-p2.x*tan60-p2.y)/(d.x*tan60+d.y);
b2=(-n*tan60-p2.x*tan60-p2.y)/(d.x*tan60+d.y);
}
if(fabs(d.y)>EPS){
t3=(n*sin60-p2.y)/d.y;
b3=(-n*sin60-p2.y)/d.y;
}
if(d.x*tan60-d.y<-EPS)swap(b1,t1);
if(d.x*tan60+d.y<-EPS)swap(b2,t2);
if(d.y<-EPS)swap(t3,b3);
if(b1-EPS<t1)cnt=min(cnt,(int)floor(t1+EPS));
if(b2-EPS<t2)cnt=min(cnt,(int)floor(t2+EPS));
if(b3-EPS<t3)cnt=min(cnt,(int)floor(t3+EPS));
return max(cnt,0);
}
// ans = gcd(p,q)+cnt+1
int main(){
int n,n1,a1,n2,a2;
scanf("%d",&n);
while(scanf("%d-%d to %d-%d",&n1,&a1,&n2,&a2)==4){
point p1=coord(n1,a1),p2=coord(n2,a2);
int p=double_to_int(p2.x-p1.x-(p2.y-p1.y)/tan60+EPS);
int q=double_to_int((p2.y-p1.y)/sin60+EPS);
printf("%d\n",__gcd(abs(p),abs(q))+extend(p1,p2,n)+1);
}
return 0;
}
完。