P3518 StrongBox

· · 题解

题目分析: 依据题意,所有的密码会形成一个模 n 的加法群 (G,+)

证明:

封闭性:a \in G, b \in G \Rightarrow a+b \mod n\in G, 这是已知条件

结合性:(a + b) + c = a + (b + c) \mod n

单位元:如果 a \in G

\Rightarrow 2*a \mod n \in G \Rightarrow 3*a \mod n \in G \Rightarrow ... \Rightarrow n*a \mod n \in G 而 $0$ 满足性质 $a + 0 = 0 + a \mod n$,所以 $0$ 就是 $(G,+)$ 的单位元。 逆元:假设 $a \in G$,现在我们来寻找 $a$的逆元,由于 $a + (n-1)*a = n*a = 0 \mod n$,并且 $(n-1)*a + a = n*a = 0 \mod n$,所以对任意元素 $a$,都存在一个逆元,为$(n-1)*a \mod n

由上可知,(G,+)是一个群。

由于逆元的存在,使得如果 a \in G,那么 -a \in G,这里的 -a 并不是一个真的负数, -a指的是 a 的逆元。为了表述方便,以下的加减乘运算也都是模n意义下的。显然如果 a \in G, b \in G \Rightarrow a - b = a + (-b) \mod n \in G

推论一:如果 a \in G, b \in G,那么 gcd(a, b) \in G

证明:由群的封闭性可知

a \in G \Rightarrow x*a \in G$,其中 $x$ 为任意整数 $(1) b \in G \Rightarrow y*b \in G$,其中 $y$ 为任意整数 $(2)

由于有逆元的存在,上述的系数 x,y 可以为负数。

结合(1),(2)可知,x*a + y*b \in G,所以 gcd(a, b) \in G

推论二:如果 a \in G,那么 gcd(n, a) \in G

证明: 不妨设 n = a*k + r,其中 kr 都是整数,并且 r < a,由数论知识可知

gcd(n, a) = gcd(a, r) a \in G \Rightarrow (k+1)*a \in G \Rightarrow a*k + a \in G \Rightarrow n - r + a \in G $ \Rightarrow r \in G \Rightarrow gcd(a, r) \in G \Rightarrow gcd(n, a) \in G

推论三:G 是一个循环群,假设 G 中最小非零元素为 g,那么集合 G = \{ 0,g,2*g,3*g,... \}

证明:

(1) 对集合 G 中的任意一个元素 x, 由推论二可知 gcd(x, g) \in G,由于g 是最小的元素,所以 g \leq gcd(g, x) \leq g,所以 g = gcd(g,x),所以 g 一定能被 x 整除,换而言之 g 一定可以整除集合 G 中的任意一个元素。

(2) 因为 g \in G \Rightarrow 2*g \in G \Rightarrow 3*g \in G \Rightarrow ... \Rightarrow g 的任意整数倍模 n 都在 G 中。当然单位元 0 也在 G 中。

结合(1)(2)可知,G 是一个循环群

推论四:假设 G 中最小非零元素为 g,那么 g 可以被 n 整除,并且 G 中元素个数 |G| = n/g

证明:

由于 g \in G,由推论二可知 gcd(n, g) \in G,又由于 g 是最小的非零元素,所以 g \leq gcd(n, g) \leq g,所以 g = gcd(n, g)g 可以被 n 整除。

由于题目中要求 G 中最大元素小于 n,结合推论三可知,G 中最大的元素为 n - g|G| = (n-g)/g + 1 = n/g

下面回到题目本身,由推论三可知,只要我们确定了生成元 g,那么集合 G就确定了,下面我们来寻找符合条件的 g。由于前 k-1 次尝试没有打开锁,所以前 k-1 次尝试的数字一定不在集合 G 中。由推论三可知: m_1, m_2, ... m_{k-1}一定都不能被 g 整除。而结合推论四可知: nm_k 都可以被 g 整除,所以gcd(n, m_k) 可以被 g 整除。 题目要求让 |G| 最大,只需要找到最小的 g 满足这个约束条件即可。

可以先计算 gcd(n, m_k),然后从1开始往上遍历筛选符合条件的 g

代码如下:

#include <stdio.h>

#define SIZE 250000
long long a[SIZE];

long long gcd(long long x, long long y) {
    long long t = 0;
    while (y != 0) {
        t = x;
        x = y;
        y = t % y;
    }
    return x;
}

int is_valid(long long x, int k) {
    int i;
    for (i = k - 1; i >= 0; i--) {
        if (a[i] % x == 0) {
            return 0;
        }
    }
    return 1;
}

int main() {
    long long n, k, b;
    long long i, j, len = 0;
    scanf("%lld%lld", &n, &k);
    k--;
    for (i = 0; i <= k; i++) {
        scanf("%lld", &a[i]);
    }

    a[k] = gcd(a[k], n);
    long long r = 0;
    long long g = 0;
    for (g = 1; g <= a[k] / g; g++) {
        if (a[k] % g != 0) {
            continue;
        }
        if (is_valid(g, k)) {
            printf("%lld\n", n / g);
            return 0;
        } else if (is_valid(a[k] / g, k)) {
            r = n / a[k] * g;
        }
    }
    printf("%lld\n", r);
    return 0;
}