K - 青春小M不会梦到兽耳娘 题解 东南大学第六届程序设计竞赛冬季赛(新手组)

K - 青春小M不会梦到兽耳娘 题解

\((0,0)\) 出发,有 \(m\) 种移动向量 \((x_i,y_i)\),每种可正反方向无限次使用。判断能否到达 \((0,1),(0,-1),(1,0),(-1,0)\) 四个点。\(T\le10^2\)\(m\le10^2\)\(|x_i|,|y_i|\le100\),每个测试点输出 YESNO

本题解核心部分参考官方题解,并使用 DeepSeek 进行部分思路梳理。

我们记录 \(L\)\(m\) 个移动向量能表示的点的集合,记 \(v_i=(x_i,y_i)\)

\[L=\{c_1v_1+c_2v_2+\cdots +c_mv_m | c_i\in \Z \} \]

首先考虑只有一个方向怎么做,即每次操作为 \(\pm x_i\),这就是典型的一次不定方程问题,可以由bezouts定理知道,只有当 \(d=\gcd(x_1,x_2,\dots,x_n)=1\) 时,才能到达 \(\pm 1\) 两个位置。

由线性代数的知识,①把一个向量减去另一个向量的整数倍,即 \(v_j\leftarrow v_j-qv_i(q\in \Z)\)\(L\) 不会改变(因为 \(v_j=(v_j-q v_i)+qv_i\) 能线性变换回来);②交换两个向量,\(L\) 也不会改变。

于是我们考虑通过线性变换,将 \(i\geq 2\)\(v_i\) 写成 \((0,y_i')\) 的形式,设经过线性变换后 \(v_1=(x,y)\) ,令 \(g_y=\gcd(y_2',y_3',\dots y_n')\)。可以将 \(L\) 表示成:

\[L=\{s(x,y)+t(0,g_y) |s,t\in \Z\} \]

当且仅当 \(|x|=1\)\(|g_y|=1\),则有解。


我们来考虑怎么对 \(v_i(i\geq 2)\) 进行变换,考虑用 \(v_1\)\(v_i\) 进行”辗转相除“,目标是使 \(v_{i}=(0,y_i')\)

首先我们需要保证 \(v_1=(x_1,y_1)\)\(x_1\neq 0\),否则无法消元。

然后求出 \(d=\lfloor \frac{x_1}{x_i}\rfloor\),即为 \(v_1\) 向量需减去 \(v_i\) 的多少倍。类比辗转相除

\[\gcd(x_1,x_i)=\gcd(x_i,x_1-dx_i) \]

于是执行

\[[v_1,v_i]\leftarrow[v_i,v_1-dv_i] \]

可以发现执行这样的过程,\(v_i\) 最终横坐标会变成 \(0\),且 \(v_1\) 的横坐标为非 \(0\),达成了我们的目的。同时对一个位置只操作了不到 \(O(\log x)\) 次。

具体实现见代码

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<int,int>ttfa;
const int N=500005;
const ll INF=0x3f3f3f3f3f3f3f3f;

int n;
ll x[N],y[N];

int main(){
	int Test;scanf("%d",&Test);
	while(Test--){
		scanf("%d",&n);
		for(int i=1;i<=n;++i){
			scanf("%lld%lld",&x[i],&y[i]);
			if(x[i])swap(x[1],x[i]),swap(y[1],y[i]);//保证第一个位置 $x$ 非 0
		}
		ll gy=0;
		for(int i=2;i<=n;++i){
			while(x[i]){//使用 (x[1],y[1]) 将这个向量的 x 方向辗转相除为 0
				ll len=x[1]/x[i];
				ll xx=x[i],yy=y[i];
				x[i]=x[1]-len*x[i],y[i]=y[1]-len*y[i];
				x[1]=xx,y[1]=yy;
				//类比 __gcd(a,b)=__gcd(b,a%b)
			}
			gy=__gcd(gy,y[i]);
		}
		if(fabs(gy)==1&&abs(x[1])==1)puts("YES");
		else puts("NO");
	}
	return 0;
}
posted @ 2026-09-21 23:51  BigSmall_En  阅读(13)  评论(0)    收藏  举报