1 条题解

  • 0
    @ 2025-8-24 21:48:50

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Alex_Wei
    **

    搬运于2025-08-24 21:48:50,当前版本为作者最后更新于2022-02-03 13:21:47,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    • Update on 2022.11.30:修改笔误。
    • Update on 2025.1.22:修订。

    *P3421 [POI2005] SKO-Knights

    题目要求我们找到两个向量 d,ed, e,使得它们的 整系数 线性组合得到的线性空间(张成)等于给出的所有向量的整系数张成。

    span(S)\mathrm{span}(S) 表示集合 SS 的整系数张成,即

    $$\mathrm{span}(S) = \left\{\sum\limits_{a_i\in S} c_ia_i :c_i\in \mathbb{Z}\right\} $$

    Sol 1

    如果我们能将三个向量 a,b,ca, b, c 合并成等价的两个向量 d,ed, e,就能解决本题。根据基础的线性代数知识,不改变张成的初等行变换有三种:

    • 交换:对应本题即任意交换 a,b,ca, b, c
    • 倍乘:对应本题即乘以任意非 00 整数。
    • 倍加:对应本题即向量加上任意整数倍其它向量。

    当然,上述性质仅在 实系数 线性组合的前提下成立。当要求系数为 整数 时:

    • 交换显然不改变张成。
    • 倍乘变换 可能改变 张成,因为整数乘法不可逆。如 span{(0,1),(1,0)}\mathrm{span}\{(0, 1), (1, 0)\} 显然不等于 span{(0,2),(1,0)}\mathrm{span}\{(0, 2), (1, 0)\}
    • 倍加变换 不改变 张成。对任意 p=y(a+xb)+zbspan(a+xb,b)p = y(a + xb) + zb\in \mathrm{span}(a + xb, b),它仍是 a,ba, b 的整系数线性组合 ya+(xy+z)bya + (xy + z)b。对任意 p=ya+zbspan(a,b)p = ya + zb \in \mathrm{span}(a, b),我们也总可以将其表示为 a+xba + xbbb 的整系数线性组合 y(a+xb)+(zxy)by(a + xb) + (z - xy) b

    很好!如果整系数倍加变换不改变整系数张成,就可以使用 辗转相减法

    具体地,考虑两个向量 a,ba, b,我们能够在不改变其整系数张成的前提下,将 bb 的某一维变为 00。只需对 a,ba, b 在这一维进行辗转相减法即可。

    考虑三个向量 a,b,ca, b, c,我们对 a,ba, bxx 这一维进行辗转相减,再对 a,ca, cxx 这一维进行辗转相减。此时 xb,xcx_b, x_c 均为 00,意味着 b,cb, c 共线。因此,对 b,cb, cyy 这一维进行辗转相减,即 ybgcd(yb,yc)y_b\gets \mathrm{gcd}(y_b, y_c),此时 cc 变成零向量,对 span(a,b,c)\mathrm{span}(a, b, c) 没有贡献,可以直接舍去,即此时 span(a,b,c)=span(a,b)\mathrm{span}(a, b, c) = \mathrm{span}(a, b)

    综上,我们在 O(nlogV)\mathcal{O}(n\log V) 的时间内解决了问题,其中 VV 是值域。

    注意题目限制坐标绝对值不超过 10410 ^ 4。辗转相减法得到最终的 aa 时,xax_ayby_b 是原坐标的 gcd\gcd,显然满足坐标限制。但 yay_a 不一定,因此要对 yby_b 取模,相当于做了一步辗转相减(此时 xb=0x_b = 0 所以不需要改变 xax_a)。最终得到的坐标绝对值不超过 10210 ^ 2,比题目限制优一个数量级。

    从上述过程中,我们发现一个有趣的性质:整系数意义下,两个向量可以在不改变其张成的前提下,使其中一个向量的某一维变成 00,而另一个向量的对应维变成原来两个向量在这一维上的 gcd\gcd。这也是解决本题的关键性质。

    Sol 2

    让我们深入地钻研一下题解区的其它做法。

    实际上两者的核心思想是等价的:将两个向量的其中一个的某一维变成 00,另一个的对应维变成 gcd\gcd。不同点在于,线性代数做法是从倍加变换和辗转相除法推得该性质,即我们使用正确性有保证的方法,最终得到的结果是这样的性质。而题解区的做法是首先令这一条件成立,再根据得到的线性方程组,使用 exgcd 和一些数学推导求解。这体现了 exgcd 与辗转相除的本质联系。

    不妨设对于向量 a,ba, b,我们要将 xbx_b 变为 00。则 xax_{a'} 只能等于 dx=gcd(xa,xb)d_x = \gcd(x_a, x_b)(不管 yy 坐标,由裴蜀定理,能达到的 xx 坐标恰为 dxd_x 的所有倍数)。因此,对于方程 xax+xby=dxx_ax + x_by = d_x,使用 扩展欧几里得 算法求得一组特解 (x0,y0)(x_0, y_0),则 ya=yax0+yby0y_{a'} = y_ax_0 + y_by_0,因为 a=x0a+y0ba' = x_0a + y_0b

    对于 bb',观察到 aa' 的通解为 $(x_0 + k\frac {x_b} {d_x}, y_0 - k\frac {x_a} {d_x})$,所以保持 xa=dxx_{a'} = d_x 不变,yay_{a'} 的最小变化量为 yaxbdxybxadx\frac {y_a x_b} {d_x} - \frac {y_b x_a}{d_x},即让 kk11yay_{a'} 产生的变化量。因为 xb=0x_{b'} = 0,所以 yby_{b'} 只能等于这个最小变化量。可以验证这样得到的 a,ba', b' 符合要求。

    综上,我们将 a,ba, b 写成了 a,ba', b',其中 xa=gcd(xa,xb)x_{a'} = \gcd(x_a, x_b)yay_{a'} 用 exgcd 求解,xb=0x_{b'} = 0yb=yaxbxaybgcd(xa,xb)y_{b'} = \frac{y_ax_b - x_ay_b}{\gcd(x_a, x_b)}。对 a,ca, cyy 坐标做类似的操作,再合并 b,cb, c 即可。

    Sol 3

    另一种求解 yby_{b'} 的思路(来源 TheLostWeak 的博客):a,ba', b' 可以整系数线性组合出 a,ba, b

    考虑 aa'。因为 xa=gcd(xa,xb)x_{a'} = \gcd(x_a, x_b),所以为了使横坐标等于 xax_a,需要将 aa' 乘以 xagcd(xa,xb)\frac{x_a}{\gcd(x_a, x_b)}xaxa\frac{x_a}{x_{a'}} 的系数,得到 (xa,xayaxa)(x_a, \frac{x_ay_{a'}}{x_{a'}})。因为可以通过向量 b=(0,yb)b' = (0, y_{b'}) 得到 (xa,ya)(x_a, y_a),因此 yb yaxayaxay_{b'} \mid \ y_a - \frac{x_ay_{a'}}{x_{a'}} 。同理 yb ybxbyaxay_{b'} \mid \ y_b - \frac{x_by_{a'}}{x_{a'}}。因此

    $$y_{b'} = \frac {\gcd(y_ax_{a'} - x_a y_{a'}, y_bx_{a'} - x_by_{a'})} {x_{a'}} $$

    yby_{b'} 小于上述 gcd\gcd,将导致存在 pspan(a,b)p\in \mathrm{span}(a', b')pspan(a,b)p\notin \mathrm{span}(a, b)。若 yby_{b'} 大于上述 gcd\gcda,ba', b' 无法整系数线性组合出 a,ba, b 中的至少一个。

    抛砖引玉:联立后两种方法描述 yby_{b'} 的式子,稍作化简后得到等式

    $$y_ax_b - x_ay_b = \gcd(y_ax_{a'} - x_a y_{a'}, {y_bx_{a'} - x_by_{a'}}) $$

    下方是线性代数辗转相除法的代码,非常容易理解。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 100 + 5;
    int n, a[N], b[N];
    int main() {
    	cin >> n >> a[1] >> b[1];
    	for(int i = 2; i <= n; i++) {
    		cin >> a[i] >> b[i];
    		while(a[i]) {
    			if(abs(a[1]) < abs(a[i])) swap(a[1], a[i]), swap(b[1], b[i]);
    			else b[1] -= b[i] * (a[1] / a[i]), a[1] %= a[i];
    		} if(i > 2) b[2] = __gcd(b[2], b[i]), b[1] %= b[2];
    	} cout << a[1] << " " << b[1] << endl << a[2] << " " << b[2] << endl;
    	return 0;
    }
    
    • 1

    信息

    ID
    2498
    时间
    1000ms
    内存
    125MiB
    难度
    5
    标签
    递交数
    0
    已通过
    0
    上传者